Source code of Windows XP (NT5)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1872 lines
51 KiB

  1. /****************************************************************************
  2. *
  3. * AVIVIEW.C
  4. *
  5. * Sample program using the AVIFile read/write routines
  6. *
  7. * Copyright (c) 1992 Microsoft Corporation. All Rights Reserved.
  8. *
  9. * You have a royalty-free right to use, modify, reproduce and
  10. * distribute the Sample Files (and/or any modified version) in
  11. * any way you find useful, provided that you agree that
  12. * Microsoft has no warranty obligations or liability for any
  13. * Sample Application Files which are modified.
  14. *
  15. ***************************************************************************/
  16. #include <windows.h>
  17. #include <windowsx.h>
  18. #include <win32.h>
  19. #include <commdlg.h>
  20. #include <vfw.h>
  21. #include <msacm.h>
  22. #include "aviview.h"
  23. #include "aviplay.h"
  24. #include "audplay.h"
  25. #ifdef WIN32
  26. #define muldiv32 MulDiv
  27. #else
  28. extern LONG FAR PASCAL muldiv32(LONG, LONG, LONG);
  29. #endif
  30. #define GlobalSizePtr(lp) GlobalSize(GlobalPtrHandle(lp))
  31. /*----------------------------------------------------------------------------*\
  32. \*----------------------------------------------------------------------------*/
  33. typedef LONG (FAR PASCAL *LPWNDPROC)(HWND, UINT, WPARAM, LPARAM); // pointer to a window procedure
  34. /*----------------------------------------------------------------------------*\
  35. \*----------------------------------------------------------------------------*/
  36. static TCHAR gszAppName[]=TEXT("AVIView");
  37. HINSTANCE ghInstApp;
  38. HWND ghwndApp;
  39. HACCEL ghAccel;
  40. #define SCROLLRANGE 10000
  41. BOOL gfDecompress = TRUE, gfCheat = TRUE;
  42. BOOL gfYieldBound, gfReadBound, gfDecompressBound, gfDrawBound;
  43. #define MAXNUMSTREAMS 10
  44. PAVIFILE gpfile; // the current file
  45. PAVISTREAM gapavi[MAXNUMSTREAMS]; // the current streams
  46. AVICOMPRESSOPTIONS gaAVIOptions[MAXNUMSTREAMS];// compression options
  47. LPAVICOMPRESSOPTIONS galpAVIOptions[MAXNUMSTREAMS];
  48. PGETFRAME gapgf[MAXNUMSTREAMS]; // data for decompressing video
  49. HDRAWDIB ghdd[MAXNUMSTREAMS]; // drawdib handles
  50. int gcpavi; // # of streams
  51. PAVISTREAM gpaviAudio; // 1st audio stream found
  52. PAVISTREAM gpaviVideo; // 1st video stream found
  53. #define gfVideoFound (gpaviVideo != NULL)
  54. #define gfAudioFound (gpaviAudio != NULL)
  55. BOOL gfPlaying; // are we currently playing?
  56. LONG glPlayStartTime; // When did we start playing?
  57. LONG glPlayStartPos; // Where were we on the scrollbar?
  58. LONG timeStart; // cached start, end, length
  59. LONG timeEnd;
  60. LONG timeLength;
  61. LONG timehscroll; // how much arrows scroll HORZ bar
  62. LONG vertSBLen; // vertical scroll bar
  63. LONG vertHeight;
  64. DWORD gdwMicroSecPerPixel = 1000L; // scale for video
  65. TCHAR gachFileName[128] = TEXT("");
  66. TCHAR gachSaveFileName[128] = TEXT("");
  67. WORD gwZoom = 2; // one-half zoom (divide by 4)
  68. // constants for painting
  69. #define VSPACE 8 // some vertical spacing
  70. #define HSPACE 4 // space between frames for video stream
  71. #define TSPACE 20 // space for text area about each stream
  72. #define AUDIOVSPACE 64 // height of an audio stream at X1 zoom
  73. /*----------------------------------------------------------------------------*\
  74. \*----------------------------------------------------------------------------*/
  75. // Macros to get and set the scroll bar to a given millisecond value in the
  76. // movie. Movie lengths can be DWORDS but we only have 16 bits of resolution.
  77. #define GetScrollTime(hwnd) \
  78. (timeStart + muldiv32(GetScrollPos(hwnd, SB_HORZ), timeLength, SCROLLRANGE))
  79. #define SetScrollTime(hwnd, time) SetScrollPos(hwnd, SB_HORZ, \
  80. (int)muldiv32((time) - timeStart, SCROLLRANGE, timeLength), TRUE)
  81. /*----------------------------------------------------------------------------*\
  82. \*----------------------------------------------------------------------------*/
  83. LONG FAR PASCAL _export AppWndProc (HWND hwnd, unsigned uiMessage, WPARAM wParam, LPARAM lParam);
  84. int ErrMsg (LPTSTR sz,...);
  85. LONG NEAR PASCAL AppCommand(HWND hwnd, unsigned msg, WPARAM wParam, LPARAM lParam);
  86. /*----------------------------------------------------------------------------*\
  87. \*----------------------------------------------------------------------------*/
  88. int fWait = 0;
  89. //
  90. // Start a wait operation... put up the hourglass if it's the first call
  91. //
  92. void StartWait()
  93. {
  94. if (fWait++ == 0) {
  95. SetCursor(LoadCursor(NULL,IDC_WAIT));
  96. }
  97. }
  98. //
  99. // Once every one who started a wait is finished, go back to regular cursor
  100. //
  101. void EndWait()
  102. {
  103. if (--fWait == 0) {
  104. SetCursor(LoadCursor(NULL,IDC_ARROW));
  105. InvalidateRect(ghwndApp, NULL, TRUE);
  106. }
  107. }
  108. //
  109. // Code to yield while we're not calling GetMessage.
  110. // Dispatch all messages. Pressing ESC or closing aborts.
  111. //
  112. BOOL WinYield()
  113. {
  114. MSG msg;
  115. BOOL fAbort=FALSE;
  116. while(fWait > 0 && PeekMessage(&msg,NULL,0,0,PM_REMOVE))
  117. {
  118. if (msg.message == WM_KEYDOWN && msg.wParam == VK_ESCAPE)
  119. fAbort = TRUE;
  120. if (msg.message == WM_SYSCOMMAND && (msg.wParam & 0xFFF0) == SC_CLOSE)
  121. fAbort = TRUE;
  122. TranslateMessage(&msg);
  123. DispatchMessage(&msg);
  124. }
  125. return fAbort;
  126. }
  127. /*----------------------------------------------------------------------------*\
  128. \*----------------------------------------------------------------------------*/
  129. //
  130. // When we load a file or zoom changes, we re-set the scrollbars
  131. //
  132. void FixScrollbars(HWND hwnd)
  133. {
  134. AVISTREAMINFO avis;
  135. LONG r, lHeight = 0;
  136. UINT w;
  137. int i;
  138. RECT rc;
  139. //
  140. // Walk through all streams and determine how many pixels it will take to
  141. // draw it.
  142. //
  143. for (i = 0; i < gcpavi; i++) {
  144. AVIStreamInfo(gapavi[i], &avis, sizeof(avis));
  145. if (avis.fccType == streamtypeVIDEO) {
  146. //
  147. // Set the horizontal scrollbar scale to show every frame
  148. // of the first video stream exactly once
  149. //
  150. if (gapavi[i] == gpaviVideo) {
  151. w = (avis.rcFrame.right - avis.rcFrame.left) * gwZoom / 4 +
  152. HSPACE;
  153. r = (LONG)(avis.dwRate / avis.dwScale);
  154. gdwMicroSecPerPixel = muldiv32(1000000, 1, w * r);
  155. timehscroll = 1000 / r; // msec per frame
  156. }
  157. lHeight += TSPACE +
  158. (avis.rcFrame.bottom - avis.rcFrame.top) * gwZoom / 4 +
  159. TSPACE;
  160. } else if (avis.fccType == streamtypeAUDIO) {
  161. lHeight += TSPACE + AUDIOVSPACE * gwZoom / 4;
  162. }
  163. //
  164. // Every stream has this much space
  165. //
  166. lHeight += TSPACE + VSPACE;
  167. }
  168. //
  169. // Set vertical scrollbar for scrolling the visible area
  170. //
  171. GetClientRect(hwnd, &rc);
  172. vertHeight = lHeight; // total height in pixels of entire display
  173. //
  174. // We won't fit in the window... need scrollbars
  175. //
  176. if (lHeight > rc.bottom) {
  177. vertSBLen = lHeight - rc.bottom;
  178. SetScrollRange(hwnd, SB_VERT, 0, (int)vertSBLen, TRUE);
  179. SetScrollPos(hwnd, SB_VERT, 0, TRUE);
  180. //
  181. // We will fit in the window! No scrollbars necessary
  182. //
  183. } else {
  184. vertSBLen = 0;
  185. SetScrollRange(hwnd, SB_VERT, 0, 0, TRUE);
  186. }
  187. }
  188. //
  189. // Initialize the streams of a loaded file -- the compression options, the
  190. // DrawDIB handles, and the scroll bars
  191. //
  192. void InitStreams(HWND hwnd)
  193. {
  194. AVISTREAMINFO avis;
  195. LONG lTemp;
  196. int i;
  197. //
  198. // Start with bogus times
  199. //
  200. timeStart = 0x7FFFFFFF;
  201. timeEnd = 0;
  202. //
  203. // Walk through and init all streams loaded
  204. //
  205. for (i = 0; i < gcpavi; i++) {
  206. AVIStreamInfo(gapavi[i], &avis, sizeof(avis));
  207. //
  208. // Save and SaveOptions code takes a pointer to our compression opts
  209. //
  210. galpAVIOptions[i] = &gaAVIOptions[i];
  211. //
  212. // clear options structure to zeroes
  213. //
  214. _fmemset(galpAVIOptions[i], 0, sizeof(AVICOMPRESSOPTIONS));
  215. //
  216. // Initialize the compression options to some default stuff
  217. // !!! Pick something better
  218. //
  219. galpAVIOptions[i]->fccType = avis.fccType;
  220. switch(avis.fccType) {
  221. case streamtypeVIDEO:
  222. galpAVIOptions[i]->dwFlags = AVICOMPRESSF_VALID |
  223. AVICOMPRESSF_KEYFRAMES | AVICOMPRESSF_DATARATE;
  224. galpAVIOptions[i]->fccHandler = mmioFOURCC('M', 'S', 'V', 'C');
  225. galpAVIOptions[i]->dwQuality = (DWORD)ICQUALITY_DEFAULT;
  226. galpAVIOptions[i]->dwKeyFrameEvery = 7; // !!! ask compressor?
  227. galpAVIOptions[i]->dwBytesPerSecond = 60000;
  228. break;
  229. case streamtypeAUDIO:
  230. galpAVIOptions[i]->dwFlags |= AVICOMPRESSF_VALID;
  231. galpAVIOptions[i]->dwInterleaveEvery = 5;
  232. #ifdef USE_ACM
  233. acmMetrics(NULL,
  234. ACM_METRIC_MAX_SIZE_FORMAT,
  235. (LPVOID) &galpAVIOptions[i]->cbFormat);
  236. #endif
  237. galpAVIOptions[i]->lpFormat =
  238. GlobalAllocPtr(GHND, galpAVIOptions[i]->cbFormat);
  239. lTemp = galpAVIOptions[i]->cbFormat;
  240. // Use current format as default format
  241. AVIStreamReadFormat(gapavi[i], 0,
  242. galpAVIOptions[i]->lpFormat,
  243. &lTemp);
  244. break;
  245. default:
  246. break;
  247. }
  248. //
  249. // We're finding the earliest and latest start and end points for
  250. // our scrollbar.
  251. //
  252. timeStart = min(timeStart, AVIStreamStartTime(gapavi[i]));
  253. timeEnd = max(timeEnd, AVIStreamEndTime(gapavi[i]));
  254. //
  255. // Initialize video streams for getting decompressed frames to display
  256. //
  257. if (avis.fccType == streamtypeVIDEO) {
  258. gapgf[i] = AVIStreamGetFrameOpen(gapavi[i], NULL);
  259. if (gapgf[i] == NULL)
  260. continue;
  261. ghdd[i] = DrawDibOpen();
  262. // !!! DrawDibBegin?
  263. if (gpaviVideo == NULL) {
  264. //
  265. // Remember the first video stream --- treat it specially
  266. //
  267. gpaviVideo = gapavi[i];
  268. }
  269. } else if (avis.fccType == streamtypeAUDIO) {
  270. //
  271. // Remember the first audio stream --- treat it specially
  272. //
  273. if (gpaviAudio == NULL)
  274. gpaviAudio = gapavi[i];
  275. }
  276. }
  277. timeLength = timeEnd - timeStart;
  278. SetScrollRange(hwnd, SB_HORZ, 0, SCROLLRANGE, TRUE);
  279. SetScrollTime(hwnd, timeStart);
  280. FixScrollbars(hwnd);
  281. }
  282. //
  283. // Update the window title to reflect what's loaded
  284. //
  285. void FixWindowTitle(HWND hwnd)
  286. {
  287. TCHAR ach[80];
  288. wsprintf(ach, TEXT("%s %s"),
  289. (LPTSTR)gszAppName,
  290. (LPTSTR)gachFileName);
  291. SetWindowText(hwnd, ach);
  292. InvalidateRect(hwnd, NULL, TRUE);
  293. }
  294. //
  295. // Free up the resources associated with DrawDIB
  296. //
  297. void FreeDrawStuff(HWND hwnd)
  298. {
  299. int i;
  300. // Make sure we're not playing!
  301. aviaudioStop();
  302. for (i = 0; i < gcpavi; i++) {
  303. if (gapgf[i]) {
  304. AVIStreamGetFrameClose(gapgf[i]);
  305. gapgf[i] = NULL;
  306. }
  307. if (ghdd[i]) {
  308. DrawDibClose(ghdd[i]);
  309. ghdd[i] = 0;
  310. }
  311. }
  312. SetScrollRange(hwnd, SB_HORZ, 0, 0, TRUE);
  313. gpaviVideo = gpaviAudio = NULL;
  314. }
  315. //
  316. // Free the resources associated with an open file
  317. //
  318. void FreeAvi(HWND hwnd)
  319. {
  320. int i;
  321. FreeDrawStuff(hwnd);
  322. for (i = 0; i < gcpavi; i++) {
  323. AVIStreamClose(gapavi[i]);
  324. if (galpAVIOptions[i]->lpFormat) {
  325. GlobalFreePtr(galpAVIOptions[i]->lpFormat);
  326. }
  327. }
  328. if (gpfile)
  329. AVIFileClose(gpfile);
  330. gpfile = NULL;
  331. gcpavi = 0;
  332. }
  333. //
  334. // Open up our fake "ball" file as an installible stream hander
  335. // !!! Document how someone goes about writing their own !!!
  336. //
  337. void InitBall(HWND hwnd)
  338. {
  339. PAVISTREAM FAR PASCAL NewBall(void);
  340. // close everything down
  341. FreeAvi(hwnd);
  342. //
  343. // The NewBall() function creates a PAVISTREAM we can use as it it was
  344. // an AVI file.
  345. //
  346. gapavi[0] = NewBall();
  347. if (gapavi[0])
  348. gcpavi = 1;
  349. lstrcpy(gachFileName, TEXT("BALL"));
  350. InitStreams(hwnd);
  351. FixWindowTitle(hwnd);
  352. }
  353. //
  354. // Open up a file through the AVIFile handlers
  355. //
  356. void InitAvi(HWND hwnd, LPTSTR szFile, UINT wMenu)
  357. {
  358. HRESULT hr;
  359. int i;
  360. PAVIFILE pfile;
  361. //
  362. // If we're opening something new, close other open files, otherwise
  363. // just close the draw stuff so we'll merge streams with the new file
  364. //
  365. if (wMenu == MENU_OPEN)
  366. FreeAvi(hwnd);
  367. else
  368. FreeDrawStuff(hwnd);
  369. hr = AVIFileOpen(&pfile, szFile, 0, 0L);
  370. if (hr != 0)
  371. goto error;
  372. //
  373. // Get all the streams from the new file
  374. //
  375. for (i = gcpavi; i < MAXNUMSTREAMS; i++) {
  376. if (AVIFileGetStream(pfile, &gapavi[i], 0L, i - gcpavi) != AVIERR_OK)
  377. break;
  378. }
  379. //
  380. // Something went wrong before we finished
  381. //
  382. if (gcpavi == i)
  383. {
  384. error:
  385. ErrMsg(TEXT("Unable to open %s"), (LPTSTR)szFile);
  386. if (pfile)
  387. AVIFileClose(pfile);
  388. return;
  389. }
  390. gcpavi = i;
  391. // !!! Play code won't see merged streams
  392. if (!gpfile)
  393. gpfile = pfile;
  394. else
  395. AVIFileClose(pfile);
  396. InitStreams(hwnd);
  397. FixWindowTitle(hwnd);
  398. }
  399. /*----------------------------------------------------------------------------*\
  400. | AppInit( hInst, hPrev) |
  401. | |
  402. | Description: |
  403. | This is called when the application is first loaded into |
  404. | memory. It performs all initialization that doesn't need to be done |
  405. | once per instance. |
  406. | |
  407. | Arguments: |
  408. | hInstance instance handle of current instance |
  409. | hPrev instance handle of previous instance |
  410. | |
  411. | Returns: |
  412. | TRUE if successful, FALSE if not |
  413. | |
  414. \*----------------------------------------------------------------------------*/
  415. BOOL AppInit(HINSTANCE hInst, HINSTANCE hPrev, UINT sw, LPSTR szCmdLine)
  416. {
  417. WNDCLASS cls;
  418. //
  419. // Save instance handle for DialogBoxs
  420. //
  421. ghInstApp = hInst;
  422. ghAccel = LoadAccelerators(hInst, MAKEINTATOM(ID_APP));
  423. #ifndef UNICODE // command line is ansi - can't be bothered to convert it
  424. //
  425. // Did we get passed a filename on the command line? We'll open it at create
  426. // time.
  427. //
  428. if (szCmdLine && szCmdLine[0])
  429. lstrcpy(gachFileName, szCmdLine);
  430. #endif
  431. if (!hPrev) {
  432. /*
  433. * Register a class for the main application window
  434. */
  435. cls.hCursor = LoadCursor(NULL,IDC_ARROW);
  436. cls.hIcon = LoadIcon(hInst,MAKEINTATOM(ID_APP));
  437. cls.lpszMenuName = MAKEINTATOM(ID_APP);
  438. cls.lpszClassName = MAKEINTATOM(ID_APP);
  439. cls.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
  440. cls.hInstance = hInst;
  441. cls.style = CS_BYTEALIGNCLIENT | CS_VREDRAW | CS_HREDRAW |
  442. CS_DBLCLKS;
  443. cls.lpfnWndProc = (LPWNDPROC)AppWndProc;
  444. cls.cbWndExtra = 0;
  445. cls.cbClsExtra = 0;
  446. if (!RegisterClass(&cls))
  447. return FALSE;
  448. }
  449. //
  450. // Must be called before using any of the AVIFile routines
  451. //
  452. AVIStreamInit();
  453. //
  454. // Create our main application window
  455. //
  456. ghwndApp = CreateWindow (
  457. MAKEINTATOM(ID_APP), // Class name
  458. gszAppName, // Caption
  459. WS_OVERLAPPEDWINDOW, // Style bits
  460. CW_USEDEFAULT, 0, // Position
  461. 320,300, // Size
  462. (HWND)NULL, // Parent window (no parent)
  463. (HMENU)NULL, // use class menu
  464. hInst, // handle to window instance
  465. (LPSTR)NULL // no params to pass on
  466. );
  467. ShowWindow(ghwndApp,sw);
  468. return TRUE;
  469. }
  470. /*----------------------------------------------------------------------------*\
  471. | WinMain( hInst, hPrev, lpszCmdLine, cmdShow ) |
  472. | |
  473. | Description: |
  474. | The main procedure for the App. After initializing, it just goes |
  475. | into a message-processing loop until it gets a WM_QUIT message |
  476. | (meaning the app was closed). |
  477. | |
  478. | Arguments: |
  479. | hInst instance handle of this instance of the app |
  480. | hPrev instance handle of previous instance, NULL if first |
  481. | szCmdLine ->null-terminated command line |
  482. | cmdShow specifies how the window is initially displayed |
  483. | |
  484. | Returns: |
  485. | The exit code as specified in the WM_QUIT message. |
  486. | |
  487. \*----------------------------------------------------------------------------*/
  488. int PASCAL WinMain(HINSTANCE hInst, HINSTANCE hPrev, LPSTR szCmdLine, int sw)
  489. {
  490. MSG msg;
  491. //
  492. // Call our initialization procedure
  493. //
  494. if (!AppInit(hInst, hPrev, sw, szCmdLine))
  495. return FALSE;
  496. /*
  497. * Polling messages from event queue
  498. */
  499. for (;;)
  500. {
  501. if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
  502. {
  503. if (msg.message == WM_QUIT)
  504. break;
  505. if (TranslateAccelerator(ghwndApp, ghAccel, &msg))
  506. continue;
  507. TranslateMessage(&msg);
  508. DispatchMessage(&msg);
  509. }
  510. //
  511. // If we have no messages to dispatch, we do our background task...
  512. // If we're playing a file, we set the scroll bar to show the video
  513. // frames corresponding with the current playing audio sample
  514. //
  515. else if (gfPlaying) {
  516. LONG l;
  517. //
  518. // Use the audio clock to tell how long we've been playing. To
  519. // maintain sync, it's important we use this clock.
  520. //
  521. l = aviaudioTime(); // returns -1 if no audio playing
  522. //
  523. // If we can't use the audio clock to tell us how long we've been
  524. // playing, calculate it ourself
  525. //
  526. if (l == -1)
  527. l = timeGetTime() - glPlayStartTime + glPlayStartPos;
  528. if (l != GetScrollTime(ghwndApp)) {
  529. if (l < timeStart) // make sure number isn't out of bounds
  530. l = timeStart;
  531. if (l > timeEnd) // looks like we're all done!
  532. gfPlaying = FALSE;
  533. SetScrollTime(ghwndApp, l);
  534. InvalidateRect(ghwndApp, NULL, FALSE);
  535. UpdateWindow(ghwndApp);
  536. continue;
  537. }
  538. WaitMessage();
  539. }
  540. }
  541. return msg.wParam;
  542. }
  543. typedef BYTE _huge * HPBYTE;
  544. typedef int _huge * HPINT;
  545. //
  546. // Draw a video frame in the specified rect
  547. //
  548. void PaintVideo(HDC hdc, RECT rcFrame, int iStream, LPBITMAPINFOHEADER lpbi, int iCurFrame, LONG lPos)
  549. {
  550. int iLen;
  551. TCHAR ach[200];
  552. RECT rc;
  553. //
  554. // If we have a picture, draw it
  555. //
  556. if (lpbi)
  557. {
  558. //
  559. // use the palette of the first video stream
  560. //
  561. DrawDibDraw(ghdd[iStream], hdc,
  562. rcFrame.left, rcFrame.top,
  563. rcFrame.right - rcFrame.left,
  564. rcFrame.bottom - rcFrame.top,
  565. lpbi, NULL,
  566. 0, 0, -1, -1,
  567. gapavi[iStream] == gpaviVideo ? 0 :DDF_BACKGROUNDPAL);
  568. iLen = wsprintf(ach, TEXT("%d %ld.%03lds"),
  569. iCurFrame, lPos / 1000, lPos % 1000);
  570. }
  571. //
  572. // Before or after the movie (or read error) draw GRAY
  573. //
  574. else {
  575. SelectObject(hdc,GetStockObject(DKGRAY_BRUSH));
  576. PatBlt(hdc,
  577. rcFrame.left, rcFrame.top,
  578. rcFrame.right - rcFrame.left,
  579. rcFrame.bottom - rcFrame.top,
  580. PATCOPY);
  581. iLen = 0;
  582. ach[0] = TEXT('\0');
  583. }
  584. //
  585. // print something meaningful under the frame
  586. //
  587. rc.left = rcFrame.left;
  588. rc.right = rcFrame.right + HSPACE;
  589. rc.top = rcFrame.bottom + VSPACE;
  590. rc.bottom = rc.top + TSPACE;
  591. ExtTextOut(hdc, rc.left, rc.top, ETO_OPAQUE,
  592. &rc, ach, iLen, NULL);
  593. }
  594. //
  595. // Draw some samples of audio inside the given rectangle
  596. //
  597. void PaintAudio(HDC hdc, PRECT prc, PAVISTREAM pavi, LONG lStart, LONG lLen)
  598. {
  599. PCMWAVEFORMAT wf;
  600. int i;
  601. int x,y;
  602. int w,h;
  603. BYTE b;
  604. HBRUSH hbr;
  605. RECT rc = *prc;
  606. LONG lBytes;
  607. LONG l, lLenOrig = lLen;
  608. LONG lWaveBeginTime = AVIStreamStartTime(pavi);
  609. LONG lWaveEndTime = AVIStreamEndTime(pavi);
  610. static LPVOID lpAudio = NULL;
  611. //
  612. // We can't draw before the beginning of the stream - adjust
  613. //
  614. if (lStart < lWaveBeginTime) {
  615. lLen -= lWaveBeginTime - lStart;
  616. lStart = lWaveBeginTime;
  617. // right justify the legal samples in the rectangle - don't stretch
  618. rc.left = rc.right - (int)muldiv32(rc.right - rc.left, lLen, lLenOrig);
  619. }
  620. //
  621. // We can't draw past the end of the stream
  622. //
  623. if (lStart + lLen > lWaveEndTime) {
  624. lLenOrig = lLen;
  625. lLen = lWaveEndTime - lStart;
  626. // left justify the legal samples in the rectangle - don't stretch
  627. rc.right = rc.left + (int)muldiv32(rc.right - rc.left, lLen, lLenOrig);
  628. }
  629. // Now start working with samples, not time
  630. l = lStart;
  631. lStart = AVIStreamTimeToSample(pavi, lStart);
  632. lLen = AVIStreamTimeToSample(pavi, l + lLen) - lStart;
  633. //
  634. // Get the format of the wave data
  635. //
  636. l = sizeof(wf);
  637. AVIStreamReadFormat(pavi, lStart, &wf, &l);
  638. if (!l)
  639. return;
  640. w = rc.right - rc.left;
  641. h = rc.bottom - rc.top;
  642. //
  643. // We were starting before the beginning or continuing past the end.
  644. // We're not painting in the whole original rect --- use a dark background
  645. //
  646. if (rc.left > prc->left) {
  647. SelectObject(hdc, GetStockObject(DKGRAY_BRUSH));
  648. PatBlt(hdc, prc->left, rc.top, rc.left - prc->left,
  649. rc.bottom - rc.top, PATCOPY);
  650. }
  651. if (rc.right < prc->right) {
  652. SelectObject(hdc, GetStockObject(DKGRAY_BRUSH));
  653. PatBlt(hdc, rc.right, rc.top, prc->right - rc.right,
  654. rc.bottom - rc.top, PATCOPY);
  655. }
  656. #define BACKBRUSH (GetSysColor(COLOR_BTNFACE)) // background
  657. #define MONOBRUSH (GetSysColor(COLOR_BTNSHADOW)) // for mono audio
  658. #define LEFTBRUSH (RGB(0,0,255)) // left channel
  659. #define RIGHTBRUSH (RGB(0,255,0)) // right channel
  660. #define HPOSBRUSH (RGB(255,0,0)) // current position
  661. //
  662. // Paint the background
  663. //
  664. hbr = SelectObject(hdc, CreateSolidBrush(BACKBRUSH));
  665. PatBlt(hdc, rc.left, rc.top, w, h, PATCOPY);
  666. DeleteObject(SelectObject(hdc, hbr));
  667. //
  668. // !!! we can only paint PCM data right now. Sorry!
  669. //
  670. if (wf.wf.wFormatTag != WAVE_FORMAT_PCM)
  671. return;
  672. //
  673. // How many bytes are we painting? Alloc some space for them
  674. // !!! These bytes never get freed
  675. //
  676. lBytes = lLen * wf.wf.nChannels * wf.wBitsPerSample / 8;
  677. if (!lpAudio)
  678. lpAudio = GlobalAllocPtr (GHND, lBytes);
  679. else if ((LONG)GlobalSizePtr(lpAudio) < lBytes)
  680. lpAudio = GlobalReAllocPtr(lpAudio, lBytes, GMEM_MOVEABLE);
  681. if (!lpAudio)
  682. return;
  683. //
  684. // Read in the wave data
  685. //
  686. AVIStreamRead(pavi, lStart, lLen, lpAudio, lBytes, NULL, &l);
  687. if (l != lLen)
  688. return;
  689. #define MulDiv(a,b,c) (UINT)((DWORD)(UINT)(a) * (DWORD)(UINT)(b) / (UINT)(c))
  690. //
  691. // !!! Flickers less painting it NOW or LATER?
  692. // First show the current position as a bar
  693. //
  694. hbr = SelectObject(hdc, CreateSolidBrush(HPOSBRUSH));
  695. PatBlt(hdc, prc->right / 2, prc->top, 1, prc->bottom - prc->top, PATCOPY);
  696. DeleteObject(SelectObject(hdc, hbr));
  697. //
  698. // Paint monochrome wave data
  699. //
  700. if (wf.wf.nChannels == 1) {
  701. //
  702. // Draw the x-axis
  703. //
  704. hbr = SelectObject(hdc, CreateSolidBrush(MONOBRUSH));
  705. y = rc.top + h/2;
  706. PatBlt(hdc, rc.left, y, w, 1, PATCOPY);
  707. //
  708. // 8 bit data is centred around 0x80
  709. //
  710. if (wf.wBitsPerSample == 8) {
  711. for (x=0; x<w; x++) {
  712. // which byte of audio data belongs at this pixel?
  713. b = *((HPBYTE)lpAudio + muldiv32(x, lLen, w));
  714. if (b > 0x80) {
  715. i = y - MulDiv(b - 0x80, (h / 2), 128);
  716. PatBlt(hdc, rc.left+x, i, 1, y-i, PATCOPY);
  717. }
  718. else {
  719. i = y + MulDiv(0x80 - b, (h / 2), 128);
  720. PatBlt(hdc, rc.left + x, y, 1, i - y, PATCOPY);
  721. }
  722. }
  723. }
  724. //
  725. // 16 bit data is centred around 0x00
  726. //
  727. else if (wf.wBitsPerSample == 16) {
  728. for (x=0; x<w; x++) {
  729. // which byte of audio data belongs at this pixel?
  730. i = *((HPINT)lpAudio + muldiv32(x,lLen,w));
  731. if (i > 0) {
  732. i = y - (int) ((LONG)i * (h/2) / 32768);
  733. PatBlt(hdc, rc.left+x, i, 1, y-i, PATCOPY);
  734. }
  735. else {
  736. i = (int) ((LONG)i * (h/2) / 32768);
  737. PatBlt(hdc, rc.left+x, y, 1, -i, PATCOPY);
  738. }
  739. }
  740. }
  741. DeleteObject(SelectObject(hdc, hbr));
  742. } // endif mono
  743. //
  744. // Draw stereo waveform data
  745. //
  746. else if (wf.wf.nChannels == 2) {
  747. //
  748. // 8 bit data is centred around 0x80
  749. //
  750. if (wf.wBitsPerSample == 8) {
  751. // Left channel
  752. hbr = SelectObject(hdc, CreateSolidBrush(LEFTBRUSH));
  753. y = rc.top + h/4;
  754. PatBlt(hdc, rc.left, y, w, 1, PATCOPY);
  755. for (x=0; x<w; x++) {
  756. b = *((HPBYTE)lpAudio + muldiv32(x,lLen,w) * 2);
  757. if (b > 0x80) {
  758. i = y - MulDiv(b-0x80,(h/4),128);
  759. PatBlt(hdc, rc.left+x, i, 1, y-i, PATCOPY);
  760. }
  761. else {
  762. i = y + MulDiv(0x80-b,(h/4),128);
  763. PatBlt(hdc, rc.left+x, y, 1, i-y, PATCOPY);
  764. }
  765. }
  766. DeleteObject(SelectObject(hdc, hbr));
  767. // Right channel
  768. hbr = SelectObject(hdc, CreateSolidBrush(RIGHTBRUSH));
  769. y = rc.top + h * 3 / 4;
  770. PatBlt(hdc, rc.left, y, w, 1, PATCOPY);
  771. for (x=0; x<w; x++) {
  772. b = *((HPBYTE)lpAudio + muldiv32(x,lLen,w) * 2 + 1);
  773. if (b > 0x80) {
  774. i = y - MulDiv(b-0x80,(h/4),128);
  775. PatBlt(hdc, rc.left+x, i, 1, y-i, PATCOPY);
  776. }
  777. else {
  778. i = y + MulDiv(0x80-b,(h/4),128);
  779. PatBlt(hdc, rc.left+x, y, 1, i-y, PATCOPY);
  780. }
  781. }
  782. DeleteObject(SelectObject(hdc, hbr));
  783. }
  784. //
  785. // 16 bit data is centred around 0x00
  786. //
  787. else if (wf.wBitsPerSample == 16) {
  788. // Left channel
  789. hbr = SelectObject(hdc, CreateSolidBrush(LEFTBRUSH));
  790. y = rc.top + h/4;
  791. PatBlt(hdc, rc.left, y, w, 1, PATCOPY);
  792. for (x=0; x<w; x++) {
  793. i = *((HPINT)lpAudio + muldiv32(x,lLen,w) * 2);
  794. if (i > 0) {
  795. i = y - (int) ((LONG)i * (h/4) / 32768);
  796. PatBlt(hdc, rc.left+x, i, 1, y-i, PATCOPY);
  797. }
  798. else {
  799. i = (int) ((LONG)i * (h/4) / 32768);
  800. PatBlt(hdc, rc.left+x, y, 1, -i, PATCOPY);
  801. }
  802. }
  803. DeleteObject(SelectObject(hdc, hbr));
  804. // Right channel
  805. hbr = SelectObject(hdc, CreateSolidBrush(RIGHTBRUSH));
  806. y = rc.top + h * 3 / 4;
  807. PatBlt(hdc, rc.left, y, w, 1, PATCOPY);
  808. for (x=0; x<w; x++) {
  809. i = *((HPINT)lpAudio + muldiv32(x,lLen,w) * 2 + 1);
  810. if (i > 0) {
  811. i = y - (int) ((LONG)i * (h/4) / 32768);
  812. PatBlt(hdc, rc.left+x, i, 1, y-i, PATCOPY);
  813. }
  814. else {
  815. i = (int) ((LONG)i * (h/4) / 32768);
  816. PatBlt(hdc, rc.left+x, y, 1, -i, PATCOPY);
  817. }
  818. }
  819. DeleteObject(SelectObject(hdc, hbr));
  820. }
  821. } // endif stereo
  822. }
  823. /*----------------------------------------------------------------------------*\
  824. | AppWndProc( hwnd, uiMessage, wParam, lParam ) |
  825. | |
  826. | Description: |
  827. | The window proc for the app's main (tiled) window. This processes all |
  828. | of the parent window's messages. |
  829. | |
  830. | Arguments: |
  831. | hwnd window handle for the window |
  832. | uiMessage message number |
  833. | wParam message-dependent |
  834. | lParam message-dependent |
  835. | |
  836. | Returns: |
  837. | 0 if processed, nonzero if ignored |
  838. | |
  839. \*----------------------------------------------------------------------------*/
  840. LONG FAR PASCAL _export AppWndProc(hwnd, msg, wParam, lParam)
  841. HWND hwnd;
  842. unsigned msg;
  843. WPARAM wParam;
  844. LPARAM lParam;
  845. {
  846. PAINTSTRUCT ps;
  847. BOOL f;
  848. HDC hdc;
  849. TCHAR ach[200];
  850. int iFrameWidth, iLen;
  851. int iFrame, iCurFrame;
  852. int n;
  853. int nFrames;
  854. LPBITMAPINFOHEADER lpbi;
  855. LONG l;
  856. LONG lTime;
  857. LONG lSize = 0;
  858. LONG lAudioStart;
  859. LONG lAudioLen;
  860. RECT rcFrame, rcC;
  861. int yStreamTop;
  862. int i;
  863. HBRUSH hbr;
  864. RECT rc;
  865. switch (msg) {
  866. //
  867. // If we passed a command line filename, open it
  868. //
  869. case WM_CREATE:
  870. if (gachFileName[0])
  871. InitAvi(hwnd, gachFileName, MENU_OPEN);
  872. break;
  873. case WM_COMMAND:
  874. return AppCommand(hwnd,msg,wParam,lParam);
  875. case WM_INITMENU:
  876. f = !gfPlaying;
  877. EnableMenuItem((HMENU)wParam, MENU_OPEN, f ? MF_ENABLED :
  878. MF_GRAYED);
  879. EnableMenuItem((HMENU)wParam, MENU_BALL, f ? MF_ENABLED :
  880. MF_GRAYED);
  881. f = (gfAudioFound || gfVideoFound) && !gfPlaying;
  882. EnableMenuItem((HMENU)wParam, MENU_SAVEAS, f ? MF_ENABLED :
  883. MF_GRAYED);
  884. EnableMenuItem((HMENU)wParam, MENU_SAVERAW,f ? MF_ENABLED :
  885. MF_GRAYED);
  886. EnableMenuItem((HMENU)wParam, MENU_OPTIONS,f ? MF_ENABLED :
  887. MF_GRAYED);
  888. f = gcpavi > 0 && !gfPlaying;
  889. EnableMenuItem((HMENU)wParam, MENU_CLOSE, f ? MF_ENABLED :
  890. MF_GRAYED);
  891. EnableMenuItem((HMENU)wParam, MENU_MERGE, f ? MF_ENABLED :
  892. MF_GRAYED);
  893. f = FALSE;
  894. EnableMenuItem((HMENU)wParam, MENU_CUT, f ? MF_ENABLED :
  895. MF_GRAYED);
  896. EnableMenuItem((HMENU)wParam, MENU_COPY, f ? MF_ENABLED :
  897. MF_GRAYED);
  898. EnableMenuItem((HMENU)wParam, MENU_DELETE, f ? MF_ENABLED :
  899. MF_GRAYED);
  900. EnableMenuItem((HMENU)wParam, MENU_PASTE, f ? MF_ENABLED :
  901. MF_GRAYED);
  902. EnableMenuItem((HMENU)wParam, MENU_MARK, f ? MF_ENABLED :
  903. MF_GRAYED);
  904. f = gfVideoFound;
  905. EnableMenuItem((HMENU)wParam, MENU_PLAY,
  906. (f & !gfPlaying && gpfile) ? MF_ENABLED : MF_GRAYED);
  907. EnableMenuItem((HMENU)wParam, MENU_STOP,
  908. (f & gfPlaying) ? MF_ENABLED : MF_GRAYED);
  909. CheckMenuItem((HMENU)wParam, MENU_ZOOMQUARTER,
  910. (gwZoom == 1) ? MF_CHECKED : MF_UNCHECKED);
  911. CheckMenuItem((HMENU)wParam, MENU_ZOOMHALF,
  912. (gwZoom == 2) ? MF_CHECKED : MF_UNCHECKED);
  913. CheckMenuItem((HMENU)wParam, MENU_ZOOM1,
  914. (gwZoom == 4) ? MF_CHECKED : MF_UNCHECKED);
  915. CheckMenuItem((HMENU)wParam, MENU_ZOOM2,
  916. (gwZoom == 8) ? MF_CHECKED : MF_UNCHECKED);
  917. CheckMenuItem((HMENU)wParam, MENU_ZOOM4,
  918. (gwZoom == 16) ? MF_CHECKED : MF_UNCHECKED);
  919. EnableMenuItem((HMENU)wParam, MENU_PLAY_DECOMPRESS,
  920. !gfPlaying ? MF_ENABLED : MF_GRAYED);
  921. EnableMenuItem((HMENU)wParam, MENU_PLAY_CHEAT,
  922. !gfPlaying ? MF_ENABLED : MF_GRAYED);
  923. CheckMenuItem((HMENU)wParam, MENU_PLAY_DECOMPRESS,
  924. gfDecompress ? MF_CHECKED : MF_UNCHECKED);
  925. CheckMenuItem((HMENU)wParam, MENU_PLAY_CHEAT,
  926. gfCheat ? MF_CHECKED : MF_UNCHECKED);
  927. CheckMenuItem((HMENU)wParam, MENU_PLAY_YIELD_BOUND,
  928. gfYieldBound ? MF_CHECKED : MF_UNCHECKED);
  929. CheckMenuItem((HMENU)wParam, MENU_PLAY_READ_BOUND,
  930. gfReadBound ? MF_CHECKED : MF_UNCHECKED);
  931. CheckMenuItem((HMENU)wParam, MENU_PLAY_DECOMPRESS_BOUND,
  932. gfDecompressBound ? MF_CHECKED : MF_UNCHECKED);
  933. CheckMenuItem((HMENU)wParam, MENU_PLAY_DRAW_BOUND,
  934. gfDrawBound ? MF_CHECKED : MF_UNCHECKED);
  935. break;
  936. //
  937. // During a wait state (eg saving) don't let us choose any menus
  938. //
  939. case WM_NCHITTEST:
  940. if (fWait) {
  941. // Let windows tell us where the cursor is
  942. lParam = DefWindowProc(hwnd,msg,wParam,lParam);
  943. // If it's over a menu, pretend it's in the client (force
  944. // hourglass)
  945. if (lParam == HTMENU)
  946. lParam = HTCLIENT;
  947. return lParam;
  948. }
  949. break;
  950. //
  951. // Set vertical scrollbar for scrolling streams
  952. //
  953. case WM_SIZE:
  954. GetClientRect(hwnd, &rc);
  955. //
  956. // There is not enough vertical room to show all streams. Scrollbars
  957. // are required.
  958. //
  959. if (vertHeight > rc.bottom) {
  960. vertSBLen = vertHeight - rc.bottom;
  961. SetScrollRange(hwnd, SB_VERT, 0, (int)vertSBLen, TRUE);
  962. //
  963. // Everything fits vertically. No scrollbar necessary.
  964. //
  965. } else {
  966. vertSBLen = 0;
  967. SetScrollRange(hwnd, SB_VERT, 0, 0, TRUE);
  968. }
  969. break;
  970. //
  971. // During a wait state, show an hourglass over our client area
  972. // !!! Is this necessary?
  973. //
  974. case WM_SETCURSOR:
  975. if (fWait && LOWORD(lParam) == HTCLIENT) {
  976. SetCursor(LoadCursor(NULL, IDC_WAIT));
  977. return TRUE;
  978. }
  979. break;
  980. //
  981. // We're out of here!
  982. //
  983. case WM_DESTROY:
  984. FreeAvi(hwnd); // close all open streams
  985. AVIStreamExit(); // shuts down the AVIFile system
  986. PostQuitMessage(0);
  987. break;
  988. //
  989. // Don't let us close ourselves in a wait state (eg saving)
  990. //
  991. case WM_CLOSE:
  992. if (fWait)
  993. return 0;
  994. break;
  995. //
  996. // Block keyboard access to menus if waiting
  997. //
  998. case WM_SYSCOMMAND:
  999. switch (wParam & 0xFFF0) {
  1000. case SC_KEYMENU:
  1001. if (fWait)
  1002. return 0;
  1003. break;
  1004. }
  1005. break;
  1006. case WM_PALETTECHANGED:
  1007. // It came from us. Ignore it
  1008. if ((HWND)wParam == hwnd)
  1009. break;
  1010. case WM_QUERYNEWPALETTE:
  1011. hdc = GetDC(hwnd);
  1012. //
  1013. // Realize the palette of the first video stream
  1014. // !!! If first stream isn't video, we're DEAD!
  1015. //
  1016. if (f = DrawDibRealize(ghdd[0], hdc, FALSE))
  1017. InvalidateRect(hwnd,NULL,TRUE);
  1018. ReleaseDC(hwnd,hdc);
  1019. return f;
  1020. case WM_ERASEBKGND:
  1021. break;
  1022. case WM_PAINT:
  1023. hdc = BeginPaint(hwnd,&ps);
  1024. GetClientRect(hwnd, &rcC);
  1025. //
  1026. // Look at scrollbars to find current position
  1027. //
  1028. lTime = GetScrollTime(hwnd);
  1029. yStreamTop = -GetScrollPos(hwnd, SB_VERT);
  1030. //
  1031. // Walk through all streams and draw something
  1032. //
  1033. for (i=0; i<gcpavi; i++) {
  1034. AVISTREAMINFO avis;
  1035. LONG lEnd, lEndTime;
  1036. //
  1037. // Get some info and print something about this stream
  1038. //
  1039. AVIStreamInfo(gapavi[i], &avis, sizeof(avis));
  1040. iLen = wsprintf(ach, TEXT("#%d[%4.4hs] Start: %ld Length: %ld "),
  1041. i,
  1042. (LPSTR) &avis.fccType, // compressor FOURCC
  1043. AVIStreamStart(gapavi[i]),
  1044. AVIStreamLength(gapavi[i]));
  1045. TextOut(hdc, HSPACE, yStreamTop, ach, iLen);
  1046. yStreamTop += TSPACE;
  1047. //
  1048. // Draw a VIDEO stream
  1049. //
  1050. if (avis.fccType == streamtypeVIDEO) {
  1051. if (gapgf[i] == NULL)
  1052. continue;
  1053. //
  1054. // Which frame belongs at this time?
  1055. //
  1056. lEndTime = AVIStreamEndTime(gapavi[i]);
  1057. if (lTime <= lEndTime)
  1058. iFrame = (int)AVIStreamTimeToSample(gapavi[i], lTime);
  1059. else {
  1060. lEnd = AVIStreamEnd(gapavi[i]);
  1061. iFrame = (int)lEnd + (int)AVIStreamTimeToSample(
  1062. gapavi[i], lTime - lEndTime);
  1063. }
  1064. //
  1065. // how wide is each frame to paint?
  1066. //
  1067. iFrameWidth = (avis.rcFrame.right - avis.rcFrame.left) *
  1068. gwZoom / 4 + HSPACE;
  1069. //
  1070. // how many frames can we fit on each half of the screen?
  1071. //
  1072. nFrames = (rcC.right - iFrameWidth) / (2 * iFrameWidth);
  1073. if (nFrames < 0)
  1074. nFrames = 0; // at least draw *something*
  1075. //
  1076. // Step through all the frames we'll draw
  1077. //
  1078. for (n = -nFrames; n <= nFrames; n++)
  1079. {
  1080. //
  1081. // Each video stream is drawn as a horizontal line of
  1082. // frames, very close together.
  1083. // The first video stream shows a different frame in
  1084. // each square. Thus the scale of time is determined
  1085. // by the first video stream.
  1086. // Every other video stream shows whatever
  1087. // frame belongs at the time corresponding to the mid-
  1088. // point of each square.
  1089. //
  1090. if (gapavi[i] == gpaviVideo) {
  1091. //
  1092. // by definition, we know what frame we're drawing..
  1093. // (iFrame-n), (iFrame-(n-1)), ..., (iFrame), ...,
  1094. // (iFrame+(n-1)), (iFrame+n)
  1095. //
  1096. iCurFrame = iFrame + n;
  1097. //
  1098. // what time is it at that frame? This number will
  1099. // be printed underneath the frame
  1100. //
  1101. l = AVIStreamSampleToTime(gapavi[i], iCurFrame);
  1102. } else { // NOT the first video stream
  1103. //
  1104. // What time is it at the midpoint of the square
  1105. // we'll draw? That's what frame we use.
  1106. //
  1107. l = lTime + muldiv32(n * iFrameWidth,
  1108. gdwMicroSecPerPixel, 1000);
  1109. //
  1110. // What frame belongs to that time?
  1111. //
  1112. iCurFrame =(int)AVIStreamTimeToSample(gapavi[i], l);
  1113. //
  1114. // what time is it at that frame? This number will
  1115. // be printed underneath the frame
  1116. //
  1117. l = AVIStreamSampleToTime(gapavi[i], iCurFrame);
  1118. }
  1119. lpbi = AVIStreamGetFrame(gapgf[i], iCurFrame);
  1120. //
  1121. // Figure out where to draw this frame
  1122. //
  1123. rcFrame.left = rcC.right / 2 -
  1124. (avis.rcFrame.right * gwZoom / 4) / 2 +
  1125. (n * iFrameWidth);
  1126. rcFrame.top = yStreamTop + TSPACE;
  1127. rcFrame.right = rcFrame.left +
  1128. avis.rcFrame.right * gwZoom / 4;
  1129. rcFrame.bottom = rcFrame.top +
  1130. avis.rcFrame.bottom * gwZoom / 4;
  1131. //
  1132. // draw a border around the current frame. Make the
  1133. // one around the centre frame a special colour.
  1134. //
  1135. if (n == 0)
  1136. hbr = CreateSolidBrush(RGB(255,0,0));
  1137. else
  1138. hbr = CreateSolidBrush(RGB(255,255,255));
  1139. InflateRect(&rcFrame, 1, 1);
  1140. FrameRect(hdc, &rcFrame, hbr);
  1141. InflateRect(&rcFrame, -1, -1);
  1142. DeleteObject (hbr);
  1143. //
  1144. // Now draw the video frame in the computed rectangle
  1145. //
  1146. PaintVideo(hdc, rcFrame, i, lpbi, iCurFrame, l);
  1147. }
  1148. //
  1149. // Print a description of this stream
  1150. //
  1151. if (lpbi)
  1152. AVIStreamSampleSize(gapavi[i], iFrame, &lSize);
  1153. iLen = wsprintf(ach,
  1154. TEXT("Frame:%d Time:%ld.%03ld sec Size:%ld bytes %s "),
  1155. iFrame, lTime/1000,lTime%1000,lSize,
  1156. (LPTSTR)(AVIStreamFindKeyFrame(gapavi[i], iFrame, 0) ==
  1157. iFrame ? TEXT("Key") : TEXT("")));
  1158. TextOut(hdc, HSPACE, yStreamTop, ach, iLen);
  1159. //
  1160. // Move down to where we can draw the next stream
  1161. //
  1162. yStreamTop += TSPACE +
  1163. (rcFrame.bottom - rcFrame.top) +
  1164. TSPACE;
  1165. }
  1166. //
  1167. // Draw an AUDIO stream
  1168. //
  1169. else if (avis.fccType == streamtypeAUDIO) {
  1170. //
  1171. // Figure out which samples are visible
  1172. //
  1173. lAudioStart = lTime - muldiv32(rcC.right / 2,
  1174. gdwMicroSecPerPixel, 1000);
  1175. lAudioLen = 2 * (lTime - lAudioStart);
  1176. //
  1177. // print something useful about this stream
  1178. //
  1179. iLen = wsprintf(ach,
  1180. TEXT("Time:%ld %ld samples shown (well, kinda) "),
  1181. lTime,
  1182. AVIStreamTimeToSample(gapavi[i], lAudioLen));
  1183. // !!! Fix the SAMPLE field for short audio clips !!!
  1184. TextOut(hdc, HSPACE, yStreamTop, ach, iLen);
  1185. //
  1186. // Make the rectangle to draw audio into
  1187. //
  1188. rc.left = rcC.left;
  1189. rc.right = rcC.right;
  1190. rc.top = yStreamTop + TSPACE;
  1191. rc.bottom = rc.top + AUDIOVSPACE * gwZoom / 4;
  1192. //
  1193. // Actually paint the audio
  1194. //
  1195. PaintAudio(hdc, &rc, gapavi[i], lAudioStart, lAudioLen);
  1196. //
  1197. // Move down to where we can draw the next stream
  1198. //
  1199. yStreamTop += TSPACE + AUDIOVSPACE * gwZoom / 4;
  1200. }
  1201. yStreamTop += VSPACE;
  1202. //
  1203. // Give up once we're painting below the bottom of the window
  1204. //
  1205. if (yStreamTop >= rcC.bottom)
  1206. break;
  1207. }
  1208. EndPaint(hwnd,&ps);
  1209. break;
  1210. //
  1211. // handle the keyboard interface
  1212. //
  1213. case WM_KEYDOWN:
  1214. switch (wParam)
  1215. {
  1216. case VK_UP: PostMessage(hwnd, WM_VSCROLL, SB_LINEUP,0L);
  1217. break;
  1218. case VK_DOWN: PostMessage(hwnd, WM_VSCROLL, SB_LINEDOWN,0L);
  1219. break;
  1220. case VK_PRIOR: PostMessage(hwnd, WM_HSCROLL, SB_PAGEUP,0L);
  1221. break;
  1222. case VK_NEXT: PostMessage(hwnd, WM_HSCROLL, SB_PAGEDOWN,0L);
  1223. break;
  1224. case VK_HOME: PostMessage(hwnd, WM_HSCROLL, SB_THUMBPOSITION,
  1225. 0L);
  1226. break;
  1227. case VK_END: PostMessage(hwnd, WM_HSCROLL, SB_THUMBPOSITION,
  1228. 0x7FFF);
  1229. break;
  1230. case VK_LEFT: PostMessage(hwnd, WM_HSCROLL, SB_LINEUP, 0L);
  1231. break;
  1232. case VK_RIGHT: PostMessage(hwnd, WM_HSCROLL, SB_LINEDOWN, 0L);
  1233. break;
  1234. }
  1235. break;
  1236. case WM_HSCROLL:
  1237. l = GetScrollTime(hwnd);
  1238. switch (GET_WM_HSCROLL_CODE(wParam, lParam)) {
  1239. case SB_LINEDOWN: l += timehscroll; break;
  1240. case SB_LINEUP: l -= timehscroll; break;
  1241. case SB_PAGEDOWN: l += timeLength/10; break;
  1242. case SB_PAGEUP: l -= timeLength/10; break;
  1243. case SB_THUMBTRACK:
  1244. case SB_THUMBPOSITION:
  1245. l = GET_WM_HSCROLL_POS(wParam, lParam);
  1246. l = timeStart + muldiv32(l, timeLength, SCROLLRANGE);
  1247. break;
  1248. }
  1249. if (l < timeStart)
  1250. l = timeStart;
  1251. if (l > timeEnd)
  1252. l = timeEnd;
  1253. if (l == GetScrollTime(hwnd))
  1254. break;
  1255. SetScrollTime(hwnd, l);
  1256. InvalidateRect(hwnd, NULL, FALSE);
  1257. UpdateWindow(hwnd);
  1258. break;
  1259. case WM_VSCROLL:
  1260. l = GetScrollPos(hwnd, SB_VERT);
  1261. GetClientRect(hwnd, &rc);
  1262. switch (GET_WM_VSCROLL_CODE(wParam, lParam)) {
  1263. case SB_LINEDOWN: l += 10; break;
  1264. case SB_LINEUP: l -= 10; break;
  1265. case SB_PAGEDOWN: l += rc.bottom; break;
  1266. case SB_PAGEUP: l -= rc.bottom; break;
  1267. case SB_THUMBTRACK:
  1268. case SB_THUMBPOSITION:
  1269. l = GET_WM_VSCROLL_POS(wParam, lParam);
  1270. break;
  1271. }
  1272. if (l < 0)
  1273. l = 0;
  1274. if (l > vertSBLen)
  1275. l = vertSBLen;
  1276. if (l == GetScrollPos(hwnd, SB_VERT))
  1277. break;
  1278. SetScrollPos(hwnd, SB_VERT, (int)l, TRUE);
  1279. InvalidateRect(hwnd, NULL, TRUE);
  1280. UpdateWindow(hwnd);
  1281. break;
  1282. //
  1283. // Wave driver wants to tell us something. Pass it on.
  1284. //
  1285. case MM_WOM_OPEN:
  1286. case MM_WOM_DONE:
  1287. case MM_WOM_CLOSE:
  1288. aviaudioMessage(hwnd, msg, wParam, lParam);
  1289. break;
  1290. }
  1291. return DefWindowProc(hwnd,msg,wParam,lParam);
  1292. }
  1293. //
  1294. // Our save callback that prints our progress in our window title bar
  1295. //
  1296. BOOL FAR PASCAL _export SaveCallback(int iProgress)
  1297. {
  1298. TCHAR ach[128];
  1299. wsprintf(ach, TEXT("%s - Saving %s: %d%%"),
  1300. (LPTSTR) gszAppName, (LPTSTR) gachSaveFileName, iProgress);
  1301. SetWindowText(ghwndApp, ach);
  1302. //
  1303. // Give ourselves a chance to abort
  1304. //
  1305. return WinYield();
  1306. }
  1307. //
  1308. // Process all of our WM_COMMAND messages
  1309. //
  1310. LONG NEAR PASCAL AppCommand (hwnd, msg, wParam, lParam)
  1311. HWND hwnd;
  1312. unsigned msg;
  1313. WPARAM wParam;
  1314. LPARAM lParam;
  1315. {
  1316. OPENFILENAME ofn;
  1317. switch(wParam)
  1318. {
  1319. //
  1320. // We want out of here!
  1321. //
  1322. case MENU_EXIT:
  1323. PostMessage(hwnd,WM_CLOSE,0,0L);
  1324. break;
  1325. //
  1326. // Set the compression options for each stream - pass an array of
  1327. // streams and an array of compression options structures
  1328. //
  1329. case MENU_OPTIONS:
  1330. AVISaveOptions(hwnd, ICMF_CHOOSE_KEYFRAME | ICMF_CHOOSE_DATARATE
  1331. | ICMF_CHOOSE_PREVIEW | ICMF_CHOOSE_ALLCOMPRESSORS,
  1332. gcpavi, gapavi, galpAVIOptions);
  1333. break;
  1334. //
  1335. // Save all the open streams into a file
  1336. //
  1337. case MENU_SAVEAS:
  1338. case MENU_SAVERAW:
  1339. gachSaveFileName[0] = 0;
  1340. //
  1341. // prompt user for file to save
  1342. //
  1343. ofn.lStructSize = sizeof(OPENFILENAME);
  1344. ofn.hwndOwner = hwnd;
  1345. ofn.hInstance = NULL;
  1346. // filter
  1347. ofn.lpstrFilter = TEXT("AVI Files\0*.avi\0WAVE Files\0*.wav\0All\0*.*\0");
  1348. ofn.lpstrCustomFilter = NULL;
  1349. ofn.nMaxCustFilter = 0;
  1350. ofn.nFilterIndex = 0;
  1351. ofn.lpstrFile = gachSaveFileName;
  1352. ofn.nMaxFile = sizeof(gachSaveFileName);
  1353. ofn.lpstrFileTitle = NULL;
  1354. ofn.nMaxFileTitle = 0;
  1355. ofn.lpstrInitialDir = NULL;
  1356. if (wParam == MENU_SAVEAS)
  1357. ofn.lpstrTitle = TEXT("Save AVI File");
  1358. else if (wParam == MENU_SAVERAW)
  1359. ofn.lpstrTitle = TEXT("Save Raw");
  1360. else
  1361. ofn.lpstrTitle = TEXT("Please give me a title");
  1362. ofn.Flags = OFN_PATHMUSTEXIST | OFN_HIDEREADONLY |
  1363. OFN_OVERWRITEPROMPT;
  1364. ofn.nFileOffset = 0;
  1365. ofn.nFileExtension = 0;
  1366. ofn.lpstrDefExt = TEXT("avi");
  1367. ofn.lCustData = 0;
  1368. ofn.lpfnHook = NULL;
  1369. ofn.lpTemplateName = NULL;
  1370. //
  1371. // If we get a filename, save it
  1372. //
  1373. if (GetSaveFileName(&ofn))
  1374. {
  1375. FARPROC lpfn = MakeProcInstance((FARPROC)SaveCallback,
  1376. ghInstApp);
  1377. if (lpfn)
  1378. {
  1379. DWORD fccHandler[MAXNUMSTREAMS];
  1380. int i;
  1381. HRESULT hr;
  1382. StartWait();
  1383. for (i = 0; i < gcpavi; i++)
  1384. fccHandler[i] = galpAVIOptions[i]->fccHandler;
  1385. // !!! This won't take away audio compression !!!
  1386. // We want to save raw -- don't use any video compression
  1387. if (wParam == MENU_SAVERAW)
  1388. for (i = 0; i < gcpavi; i++)
  1389. galpAVIOptions[i]->fccHandler = 0;
  1390. hr = AVISaveV(gachSaveFileName,
  1391. NULL,
  1392. (AVISAVECALLBACK) lpfn,
  1393. gcpavi,
  1394. gapavi,
  1395. galpAVIOptions);
  1396. if (hr != AVIERR_OK)
  1397. ErrMsg(TEXT("Error saving AVI file"));
  1398. // Now put the video compressors back that we stole
  1399. for (i = 0; i < gcpavi; i++)
  1400. galpAVIOptions[i]->fccHandler = fccHandler[i];
  1401. EndWait();
  1402. FreeProcInstance(lpfn);
  1403. FixWindowTitle(hwnd);
  1404. }
  1405. }
  1406. break;
  1407. //
  1408. // Close everything
  1409. //
  1410. case MENU_CLOSE:
  1411. FreeAvi(hwnd);
  1412. gachFileName[0] = TEXT('\0');
  1413. FixWindowTitle(hwnd);
  1414. break;
  1415. //
  1416. // Open a new file, or merge streams with a new file
  1417. //
  1418. case MENU_OPEN:
  1419. case MENU_MERGE:
  1420. gachFileName[0] = 0;
  1421. //
  1422. // prompt user for file to open
  1423. //
  1424. ofn.lStructSize = sizeof(OPENFILENAME);
  1425. ofn.hwndOwner = hwnd;
  1426. ofn.hInstance = NULL;
  1427. if (wParam == MENU_MERGE)
  1428. ofn.lpstrTitle = TEXT("Merge With");
  1429. else
  1430. ofn.lpstrTitle = TEXT("Open AVI");
  1431. // !!! This is a dumb filter
  1432. ofn.lpstrFilter = TEXT("AVI Files\0*.avi\0")
  1433. TEXT("Wave Files\0*.wav\0")
  1434. TEXT("FLI/FLC Files\0*.fli;*.flc\0")
  1435. TEXT("DIB Sequences\0*.dib;*.bmp\0")
  1436. TEXT("All\0*.*\0");
  1437. ofn.lpstrCustomFilter = NULL;
  1438. ofn.nMaxCustFilter = 0;
  1439. ofn.nFilterIndex = 0;
  1440. ofn.lpstrFile = gachFileName;
  1441. ofn.nMaxFile = sizeof(gachFileName);
  1442. ofn.lpstrFileTitle = NULL;
  1443. ofn.nMaxFileTitle = 0;
  1444. ofn.lpstrInitialDir = NULL;
  1445. ofn.Flags = OFN_FILEMUSTEXIST | OFN_PATHMUSTEXIST |OFN_HIDEREADONLY;
  1446. ofn.nFileOffset = 0;
  1447. ofn.nFileExtension = 0;
  1448. ofn.lpstrDefExt = NULL;
  1449. ofn.lCustData = 0;
  1450. ofn.lpfnHook = NULL;
  1451. ofn.lpTemplateName = NULL;
  1452. //
  1453. // If we've got a filename, go open it
  1454. //
  1455. #ifdef WIN32
  1456. if (GetOpenFileName(&ofn))
  1457. #else
  1458. if (GetOpenFileNamePreview(&ofn))
  1459. #endif
  1460. InitAvi(hwnd, gachFileName, wParam);
  1461. break;
  1462. //
  1463. // Open the "fake" ball file as our current file
  1464. //
  1465. case MENU_BALL:
  1466. InitBall(hwnd);
  1467. break;
  1468. case MENU_ZOOMQUARTER:
  1469. gwZoom = 1;
  1470. FixScrollbars(hwnd);
  1471. InvalidateRect(hwnd, NULL, TRUE);
  1472. break;
  1473. case MENU_ZOOMHALF:
  1474. gwZoom = 2;
  1475. FixScrollbars(hwnd);
  1476. InvalidateRect(hwnd, NULL, TRUE);
  1477. break;
  1478. case MENU_ZOOM1:
  1479. gwZoom = 4;
  1480. FixScrollbars(hwnd);
  1481. InvalidateRect(hwnd, NULL, TRUE);
  1482. break;
  1483. case MENU_ZOOM2:
  1484. gwZoom = 8;
  1485. FixScrollbars(hwnd);
  1486. InvalidateRect(hwnd, NULL, TRUE);
  1487. break;
  1488. case MENU_ZOOM4:
  1489. gwZoom = 16;
  1490. FixScrollbars(hwnd);
  1491. InvalidateRect(hwnd, NULL, TRUE);
  1492. break;
  1493. case MENU_PLAY_DECOMPRESS:
  1494. gfDecompress = !gfDecompress;
  1495. break;
  1496. case MENU_PLAY_CHEAT:
  1497. gfCheat = !gfCheat;
  1498. break;
  1499. case MENU_PLAY_YIELD_BOUND:
  1500. gfYieldBound = !gfYieldBound;
  1501. break;
  1502. case MENU_PLAY_READ_BOUND:
  1503. gfReadBound = !gfReadBound;
  1504. break;
  1505. case MENU_PLAY_DECOMPRESS_BOUND:
  1506. gfDecompressBound = !gfDecompressBound;
  1507. break;
  1508. case MENU_PLAY_DRAW_BOUND:
  1509. gfDrawBound = !gfDrawBound;
  1510. break;
  1511. case MENU_PLAY:
  1512. if (gfPlaying || !gpfile)
  1513. break;
  1514. gfPlaying = 1;
  1515. // Plays the file and sets the scrollbar to where play stopped
  1516. SetScrollTime(hwnd, aviPlay(hwnd, gpfile, GetScrollTime(hwnd)));
  1517. // If we stopped cuz we are closing, then close now
  1518. if (gfPlaying == -1)
  1519. PostMessage(hwnd, WM_CLOSE, 0, 0);
  1520. gfPlaying = 0;
  1521. break;
  1522. case MENU_STOP:
  1523. if (gfPlaying)
  1524. aviStop();
  1525. break;
  1526. }
  1527. return 0L;
  1528. }
  1529. /*----------------------------------------------------------------------------*\
  1530. | ErrMsg - Opens a Message box with a error message in it. The user can |
  1531. | select the OK button to continue |
  1532. \*----------------------------------------------------------------------------*/
  1533. int ErrMsg (LPTSTR sz,...)
  1534. {
  1535. static TCHAR ach[2000];
  1536. va_list va;
  1537. va_start(va, sz);
  1538. wvsprintf (ach,sz, va); /* Format the string */
  1539. va_end(va);
  1540. MessageBox(NULL,ach,NULL, MB_OK|MB_ICONEXCLAMATION|MB_TASKMODAL);
  1541. return FALSE;
  1542. }
  1543. /*****************************************************************************
  1544. *
  1545. * dprintf() is called by the DPF macro if DEBUG is defined at compile time.
  1546. *
  1547. * The messages will be send to COM1: like any debug message. To
  1548. * enable debug output, add the following to WIN.INI :
  1549. *
  1550. * [debug]
  1551. * ICSAMPLE=1
  1552. *
  1553. ****************************************************************************/
  1554. #ifdef DEBUG
  1555. #define MODNAME "AVIView"
  1556. static void FAR cdecl dprintf(LPSTR szFormat, ...)
  1557. {
  1558. char ach[128];
  1559. va_list va;
  1560. static BOOL fDebug = -1;
  1561. if (fDebug == -1)
  1562. fDebug = GetProfileIntA("Debug", MODNAME, FALSE);
  1563. if (!fDebug)
  1564. return;
  1565. lstrcpyA(ach, MODNAME ": ");
  1566. va_start(va, szFormat);
  1567. wvsprintfA(ach+lstrlenA(ach),szFormat, va);
  1568. va_end(va);
  1569. lstrcatA(ach, "\r\n");
  1570. OutputDebugStringA(ach);
  1571. }
  1572. #endif