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.

1006 lines
29 KiB

  1. /****************************************************************************
  2. *
  3. * AVISAVE.C
  4. *
  5. * routine for writing Standard AVI files
  6. *
  7. * AVISave()
  8. *
  9. * Copyright (c) 1992 Microsoft Corporation. All Rights Reserved.
  10. *
  11. * You have a royalty-free right to use, modify, reproduce and
  12. * distribute the Sample Files (and/or any modified version) in
  13. * any way you find useful, provided that you agree that
  14. * Microsoft has no warranty obligations or liability for any
  15. * Sample Application Files which are modified.
  16. *
  17. ***************************************************************************/
  18. #include <win32.h>
  19. #include <compobj.h>
  20. #include <valid.h>
  21. #include <compman.h>
  22. #include "avifmt.h"
  23. #include "avifile.h"
  24. #include "avicmprs.h"
  25. #include "debug.h"
  26. //extern LONG FAR PASCAL muldiv32(LONG,LONG,LONG);
  27. /************************************************************************/
  28. /* Auto-doc for the AVICOMPRESSOPTIONS structure. Make sure it matches */
  29. /* the declarations in avifile.h !!! */
  30. /************************************************************************/
  31. /*****************************************************************************
  32. * @doc EXTERNAL AVICOMPRESSOPTIONS
  33. *
  34. * @types AVICOMPRESSOPTIONS | This structure contains information
  35. * about a stream and how it is to be compressed and saved.
  36. * This structure passes data to <f AVIMakeCompressedStream>
  37. * (or <f AVISave> which uses <f AVIMakeCompressedStream>).
  38. *
  39. * @field DWORD | fccType | Specifies a four-character code
  40. * indicating the stream type. The following
  41. * constants have been defined for the data commonly
  42. * found in AVI streams:
  43. *
  44. * @flag streamtypeAUDIO | Indicates an audio stream.
  45. * @flag streamtypeMIDI | Indicates a MIDI stream.
  46. * @flag streamtypeTEXT | Indicates a text stream.
  47. * @flag streamtypeVIDEO | Indicates a video stream.
  48. *
  49. * @field DWORD | fccHandler | For a video stream, specifies the
  50. * four-character code for the compressor handler that
  51. * will compress this stream when it is saved
  52. * (For example, mmioFOURCC('M','S','V','C')).
  53. * This member is not used for audio streams.
  54. *
  55. * @field DWORD | dwKeyFrameEvery | Specifies the maximum period
  56. * between key frames. This member is used only
  57. * if the AVICOMPRESSF_KEYFRAMES flag is set, otherwise
  58. * every frame is a key frame.
  59. *
  60. * @field DWORD | dwQuality | Specifies the quality value passed
  61. * to a video compressor. This member is not used for
  62. * an audio compressor.
  63. *
  64. * @field DWORD | dwBytesPerSecond | Specifies the data rate a video
  65. * compressor should use. This member is used only
  66. * if the AVICOMPRESSF_DATARATE flag is set.
  67. *
  68. * @field DWORD | dwFlags | Specifies the flags used for compression:
  69. *
  70. * @flag AVICOMPRESSF_INTERLEAVE | Indicates this stream is to be interleaved
  71. * every <e AVICOMPRESSOPTIONS.dwInterleaveEvery> frames
  72. * with respect to the first stream.
  73. *
  74. * @flag AVICOMPRESSF_KEYFRAMES | Indicates this video stream
  75. * is to be saved with key frames at least
  76. * every <e AVICOMPRESSOPTIONS.dwKeyFrameEvery> frames.
  77. * By default, every frame will be a key frame.
  78. *
  79. * @flag AVICOMPRESSF_DATARATE | Indicates this video stream
  80. * is to be compressed with the data rate
  81. * specified in <e AVICOMPRESSOPTIONS.dwBytesPerSecond>.
  82. *
  83. * @flag AVICOMPRESSF_VALID | Indicates this structure contains
  84. * valid data. If this flag is set, AVIFile uses the structure
  85. * data to set the default compression values for <f AVISaveOptions>.
  86. * If an empty structure is passed and this flag is not set,
  87. * some defaults will be chosen.
  88. *
  89. * @field LPVOID | lpFormat | Specifies a pointer to a structure
  90. * defining the data format. For an audio stream,
  91. * this is an <t LPWAVEFORMAT> structure.
  92. *
  93. * @field DWORD | cbFormat | Specifies the size of the data referenced by
  94. * <e AVICOMPRESSOPTIONS.lpFormat>
  95. *
  96. * @field LPVOID | lpParms | Used internally to store compressor
  97. * specific data.
  98. *
  99. * @field DWORD | cbParms | Specifies the size of the data referenced by
  100. * <e AVICOMPRESSOPTIONS.lpParms>
  101. *
  102. * @field DWORD | dwInterleaveEvery | Specifies how often
  103. * to interleave stream data with the data
  104. * from the first stream. Used only if the
  105. * AVICOMPRESSF_INTERLEAVE flag is set.
  106. *
  107. ***************************************************************************/
  108. /*******************************************************************
  109. * @doc EXTERNAL AVISave
  110. *
  111. * @api LONG | AVISave | This function is used to save an AVI file.
  112. *
  113. * @parm LPCSTR | szFile | Specifies a zero-terminated string
  114. * containing the name of the file to save.
  115. *
  116. * @parm CLSID FAR * | pclsidHandler | Specifies a pointer to the
  117. * file handler used to write the file. The file will
  118. * be created by calling <f AVIFileOpen> using this handler. If
  119. * a handler is not specified, a default one is selected based
  120. * upon the file extension.
  121. *
  122. * @parm AVISAVECALLBACK | lpfnCallback | Specifies a far pointer to
  123. * a callback function for the save operation.
  124. *
  125. * @parm int | nStreams | Specifies the number of streams saved in the
  126. * the file.
  127. *
  128. * @parm PAVISTREAM | pavi | Specifies a pointer an AVI stream.
  129. * This parameter is paired with <p lpOptions>. The parameter
  130. * pair can be repeated as a variable number of arguments.
  131. *
  132. * @parm LPAVICOMPRESSOPTIONS | lpOptions | Specifies a pointer to an
  133. * <t AVICOMPRESSOPTIONS> structure containing the compression
  134. * options for the stream referenced by <p pavi>.
  135. * This parameter is paired with <p pavi>. The parameter
  136. * pair can be repeated as a variable number of arguments.
  137. *
  138. * @parm .| . . | Additional streams can be appened
  139. * by including more <p pavi> and <p lpOptions> parameter pairs.
  140. *
  141. * @rdesc Returns AVIERR_OK if successful; otherwise it returns an error code.
  142. *
  143. * @comm This function saves an AVI sequence to the file
  144. * specified by <p szFile>. The <p pavi> and <p lpOptions> parameters
  145. * define the streams saved. If saving more than one stream,
  146. * repeat the <p pavi> and <p lpOptions> parameter pair for
  147. * each additional stream.
  148. *
  149. * A callback function can be supplied in <p lpfnCallback> to
  150. * display status information and let the user cancel the
  151. * save operation. The callback uses the following format:
  152. *
  153. * LONG FAR PASCAL SaveCallback(int nPercent)
  154. *
  155. * The <p nPercent> parameter specifies the percentage of the
  156. * file saved.
  157. *
  158. * The callback function should return AVIERR_OK if the
  159. * operation should continue and AVIERR_USERABORT if the
  160. * user wishes to abort the save operation.
  161. *
  162. *
  163. * @xref <f AVISaveV> <f AVISaveOptions>
  164. *
  165. *******************************************************************/
  166. EXTERN_C HRESULT CDECL AVISave(LPCSTR szFile,
  167. CLSID FAR *pclsidHandler,
  168. AVISAVECALLBACK lpfnCallback,
  169. int nStreams,
  170. PAVISTREAM pavi,
  171. LPAVICOMPRESSOPTIONS lpOptions,
  172. ...
  173. )
  174. {
  175. PAVISTREAM FAR *apavi;
  176. LPAVICOMPRESSOPTIONS FAR *alpOptions;
  177. int i;
  178. HRESULT hr;
  179. //
  180. // We were passed arguments of the form PAVI, OPTIONS, PAVI, OPTIONS, etc.
  181. // for AVISaveV, we need to separate these into an array of PAVI's and
  182. // an array of LPAVICOMPRESSOPTIONS.
  183. //
  184. apavi = (PAVISTREAM FAR *)GlobalAllocPtr(GMEM_MOVEABLE,
  185. nStreams * sizeof(PAVISTREAM));
  186. alpOptions = (LPAVICOMPRESSOPTIONS FAR *)GlobalAllocPtr(GMEM_MOVEABLE,
  187. nStreams * sizeof(LPAVICOMPRESSOPTIONS));
  188. if (!apavi || !alpOptions)
  189. return ResultFromScode(AVIERR_MEMORY);
  190. for (i = 0; i < nStreams; i++) {
  191. apavi[i] = *(PAVISTREAM FAR *)((LPBYTE)&pavi +
  192. (sizeof(PAVISTREAM) + sizeof(LPAVICOMPRESSOPTIONS)) * i);
  193. alpOptions[i] = *(LPAVICOMPRESSOPTIONS FAR *)((LPBYTE)&lpOptions +
  194. (sizeof(PAVISTREAM) + sizeof(LPAVICOMPRESSOPTIONS)) * i);
  195. }
  196. hr = AVISaveV(szFile, pclsidHandler, lpfnCallback, nStreams, apavi,
  197. alpOptions);
  198. GlobalFreePtr(apavi);
  199. GlobalFreePtr(alpOptions);
  200. return hr;
  201. }
  202. BOOL FAR PASCAL DummySaveCallback(int iProgress)
  203. {
  204. return FALSE; // do nothing, allow save to continue
  205. }
  206. /**********************************************************************
  207. * @doc EXTERNAL AVISaveV
  208. *
  209. * @api LONG | AVISaveV | This function is used to save an AVI file.
  210. *
  211. * @parm LPCSTR | szFile | Specifies a zero-terminated string
  212. * containing the name of the file to save.
  213. *
  214. * @parm CLSID FAR * | pclsidHandler | Specifies a pointer to the
  215. * file handler used to write the file. The file will
  216. * be created by calling <f AVIFileOpen> using this handler. If
  217. * a handler is not specified, a default one is selected based upon
  218. * the file extension.
  219. *
  220. * @parm AVISAVECALLBACK | lpfnCallback | Specifies a pointer to a callback
  221. * function used to display status information and let the use
  222. * cancel the save operation.
  223. *
  224. * @parm int | nStreams | Specifies the number of streams to save.
  225. *
  226. * @parm PAVISTREAM FAR * | ppavi | Specifies a pointer to an
  227. * array of <t PAVISTREAM> pointers. The array uses one pointer
  228. * for each stream.
  229. *
  230. * @parm LPAVICOMPRESSOPTIONS FAR * | plpOptions | Specifies a pointer
  231. * to an array of <t LPAVICOMPRESSOPTIONS> pointers. The
  232. * uses one pointer for each stream.
  233. *
  234. * @rdesc Returns AVIERR_OK on success, an error code otherwise.
  235. *
  236. * @comm This function is equivalent to <f AVISave> except
  237. * the streams are passed in an array instead of as a
  238. * variable number of arguments. (<f AVISaveV> is to <f AVISave>
  239. * as <f wvsprintf> is to <f wsprintf>.)
  240. *
  241. * @xref <f AVISave> <f AVISaveOptions>
  242. *
  243. ********************************************************************/
  244. STDAPI AVISaveV(LPCSTR szFile,
  245. CLSID FAR *pclsidHandler,
  246. AVISAVECALLBACK lpfnCallback,
  247. int nStreams,
  248. PAVISTREAM FAR * ppavi,
  249. LPAVICOMPRESSOPTIONS FAR * plpOptions)
  250. {
  251. int stream;
  252. MainAVIHeader hdrNew;
  253. PAVIFILE pfilesave = 0;
  254. HRESULT hr;
  255. AVISTREAMINFO strhdr;
  256. AVIFILEINFO finfo;
  257. LONG cbFormat;
  258. DWORD dwSamplesRead;
  259. LPVOID lpBuffer = 0;
  260. DWORD dwBufferSize;
  261. LONG l;
  262. DWORD dwSize;
  263. DWORD dwFlags;
  264. WORD cktype;
  265. LPBITMAPINFOHEADER lpbi;
  266. DWORD dwInterleaveEvery = 0;
  267. #define MAXSTREAMS 64
  268. int iVideoStream = -1;
  269. PAVISTREAM apavi[MAXSTREAMS];
  270. PAVISTREAM apaviNew[MAXSTREAMS];
  271. LONG lDone[MAXSTREAMS];
  272. LONG lInterval;
  273. if (nStreams > MAXSTREAMS)
  274. return ResultFromScode(AVIERR_INTERNAL);
  275. for (stream = 0; stream < nStreams; stream++) {
  276. apavi[stream] = NULL;
  277. apaviNew[stream] = NULL;
  278. }
  279. //
  280. // Open file and write out the main header
  281. //
  282. DPF("Creating new file\n");
  283. hr = AVIFileOpen(&pfilesave, szFile, OF_CREATE | OF_WRITE | OF_SHARE_EXCLUSIVE, pclsidHandler);
  284. if (hr != 0)
  285. goto Error;
  286. AVIFileInfo(pfilesave, &finfo, sizeof(finfo));
  287. DPF("Creating compressed streams\n");
  288. for (stream = 0; stream < nStreams; stream++) {
  289. if (!IsValidInterface(ppavi[stream])) {
  290. hr = ResultFromScode(AVIERR_INTERNAL);
  291. goto Error;
  292. }
  293. hr = AVIStreamInfo(ppavi[stream], &strhdr, sizeof(strhdr));
  294. if (hr != AVIERR_OK) {
  295. DPF("Error from AVIStreamInfo!\n");
  296. goto Error;
  297. }
  298. // Find the video stream....
  299. if (strhdr.fccType == streamtypeVIDEO) {
  300. if (iVideoStream < 0) {
  301. iVideoStream = stream;
  302. }
  303. } else if (strhdr.fccType == streamtypeAUDIO) {
  304. if (dwInterleaveEvery == 0) {
  305. // Should the interleave factor be in the options at all?
  306. if (plpOptions && plpOptions[stream] &&
  307. plpOptions[stream]->dwFlags & AVICOMPRESSF_INTERLEAVE)
  308. dwInterleaveEvery = plpOptions[stream]->dwInterleaveEvery;
  309. }
  310. }
  311. apavi[stream] = NULL;
  312. if (plpOptions && plpOptions[stream] &&
  313. (plpOptions[stream]->fccHandler ||
  314. plpOptions[stream]->lpFormat)) {
  315. DWORD dwKeyFrameEvery = plpOptions[stream]->dwKeyFrameEvery;
  316. if (finfo.dwCaps & AVIFILECAPS_ALLKEYFRAMES)
  317. plpOptions[stream]->dwKeyFrameEvery = 1;
  318. // If they've given compression options for this stream,
  319. // use them....
  320. hr = AVIMakeCompressedStream(&apavi[stream],
  321. ppavi[stream],
  322. plpOptions[stream],
  323. NULL);
  324. plpOptions[stream]->dwKeyFrameEvery = dwKeyFrameEvery;
  325. if (hr != 0) {
  326. DPF("AVISave: Failed to create compressed stream!\n");
  327. apavi[stream] = NULL;
  328. goto Error; // !!!
  329. } else {
  330. hr = AVIStreamInfo(apavi[stream], &strhdr, sizeof(strhdr));
  331. if (hr != 0) {
  332. DPF("AVISave: Failed to create compressed stream!\n");
  333. AVIStreamClose(apavi[stream]);
  334. apavi[stream] = NULL;
  335. goto Error; // !!!
  336. }
  337. }
  338. }
  339. if (apavi[stream] == NULL) {
  340. // otherwise just copy the stream over....
  341. apavi[stream] = ppavi[stream];
  342. AVIStreamAddRef(apavi[stream]);
  343. }
  344. lDone[stream] = AVIStreamStart(apavi[stream]);
  345. }
  346. // Put the video stream first, so interleaving will work.
  347. // !!!
  348. if (iVideoStream > 0) {
  349. PAVISTREAM p;
  350. p = apavi[iVideoStream];
  351. apavi[iVideoStream] = apavi[0];
  352. apavi[0] = p;
  353. iVideoStream = 0;
  354. }
  355. if (lpfnCallback == NULL)
  356. lpfnCallback = &DummySaveCallback;
  357. /* pick a good buffer size and go for it.... */
  358. dwBufferSize = 32768L;
  359. lpBuffer = GlobalAllocPtr(GMEM_MOVEABLE, dwBufferSize);
  360. if (!lpBuffer) {
  361. hr = ResultFromScode(AVIERR_MEMORY);
  362. goto Error;
  363. }
  364. //
  365. // Construct AVI file header
  366. //
  367. AVIStreamInfo(apavi[0], &strhdr, sizeof(strhdr));
  368. hdrNew.dwMicroSecPerFrame = muldiv32(1000000L, strhdr.dwScale, strhdr.dwRate);
  369. hdrNew.dwMaxBytesPerSec = 0;
  370. hdrNew.dwPaddingGranularity = 0;
  371. hdrNew.dwFlags = AVIF_HASINDEX;
  372. hdrNew.dwFlags &= ~(AVIF_ISINTERLEAVED | AVIF_WASCAPTUREFILE |
  373. AVIF_MUSTUSEINDEX);
  374. hdrNew.dwTotalFrames = strhdr.dwLength; // !!!
  375. hdrNew.dwInitialFrames = 0; // !!!
  376. hdrNew.dwStreams = nStreams;
  377. hdrNew.dwSuggestedBufferSize = 32768;
  378. if (iVideoStream >= 0) {
  379. cbFormat = dwBufferSize;
  380. hr = AVIStreamReadFormat(apavi[iVideoStream],
  381. AVIStreamStart(apavi[iVideoStream]),
  382. lpBuffer,
  383. &cbFormat);
  384. if (cbFormat < sizeof(BITMAPINFOHEADER)) {
  385. hr = ResultFromScode(AVIERR_INTERNAL);
  386. }
  387. if (hr != 0) {
  388. DPF("AVISave: Error from initial ReadFormat!\n");
  389. goto Error;
  390. }
  391. lpbi = (LPBITMAPINFOHEADER) lpBuffer;
  392. hdrNew.dwWidth = lpbi->biWidth;
  393. hdrNew.dwHeight = lpbi->biHeight;
  394. lInterval = 1;
  395. } else {
  396. hdrNew.dwWidth = 0;
  397. hdrNew.dwHeight = 0;
  398. lInterval = AVIStreamTimeToSample(apavi[0], 500);
  399. }
  400. //
  401. // Loop through streams and write out stream header
  402. //
  403. for (stream = 0; stream < nStreams; stream++) {
  404. // DPF2("Making stream %d header LIST\n", stream);
  405. AVIStreamInfo(apavi[stream], &strhdr, sizeof(strhdr));
  406. strhdr.dwInitialFrames = 0;
  407. // If we're interleaving, skew the audio by 3/4 of a second.
  408. if (dwInterleaveEvery > 0 && stream > 0) {
  409. if (strhdr.fccType == streamtypeAUDIO) {
  410. strhdr.dwInitialFrames = AVIStreamTimeToSample(apavi[0], 750);
  411. DPF("Stream %d has %lu initial frames\n", stream, strhdr.dwInitialFrames);
  412. }
  413. }
  414. //
  415. // Get stream format and write it out
  416. //
  417. cbFormat = dwBufferSize;
  418. hr = AVIStreamReadFormat(apavi[stream], AVIStreamStart(apavi[stream]),
  419. lpBuffer, &cbFormat);
  420. if (hr != AVIERR_OK)
  421. goto Error;
  422. // !!! Overflow?
  423. if (!cbFormat) {
  424. // !!!
  425. }
  426. hr = AVIFileCreateStream(pfilesave, &apaviNew[stream], &strhdr);
  427. #if 0
  428. if (hr != AVIERR_OK || apaviNew[stream] == NULL)
  429. goto Error;
  430. #else
  431. // If we can't make a stream, continue with the other streams....
  432. if (hr != AVIERR_OK || apaviNew[stream] == NULL) {
  433. int i;
  434. DPF("AVISave: Couldn't create stream in new file!\n");
  435. AVIStreamClose(apavi[stream]);
  436. for (i = stream + 1; i < nStreams; i++) {
  437. apavi[stream] = apavi[stream + 1];
  438. }
  439. --nStreams;
  440. --stream;
  441. continue;
  442. }
  443. #endif
  444. hr = AVIStreamSetFormat(apaviNew[stream], 0, lpBuffer, cbFormat);
  445. if (hr != AVIERR_OK) {
  446. DPF("Initial set format failed!\n");
  447. goto Error;
  448. }
  449. cbFormat = dwBufferSize;
  450. hr = AVIStreamReadData(apavi[stream], ckidSTREAMHANDLERDATA,
  451. lpBuffer, &cbFormat);
  452. // !!! overflow?
  453. if (hr == AVIERR_OK && cbFormat) {
  454. /*
  455. ** Make the stream Data data chunk
  456. */
  457. // DPF2("Making stream %ld Data data chunk\n", stream);
  458. hr = AVIStreamWriteData(apaviNew[stream], ckidSTREAMHANDLERDATA,
  459. lpBuffer, cbFormat);
  460. if (hr != AVIERR_OK)
  461. goto Error;
  462. }
  463. if (strhdr.dwInitialFrames > hdrNew.dwInitialFrames)
  464. hdrNew.dwInitialFrames = strhdr.dwInitialFrames;
  465. // !!! Should call ReadExtra and WriteExtra to move over information!
  466. }
  467. if (nStreams <= 0) {
  468. DPF("No streams at all accepted by the file!\n");
  469. goto Error;
  470. }
  471. //
  472. // We've written the header. Now, there are two possibilities:
  473. //
  474. // 1.) File is interleaved. We loop in time from beginning to end,
  475. // then loop through the streams and write out any data for the
  476. // current time.
  477. //
  478. // 2.) File is not interleaved. We loop through the streams and
  479. // write each one out separately.
  480. //
  481. if (dwInterleaveEvery > 0) {
  482. DPF("Saving interleaved: factor = %lu, intial = %lu, total = %lu\n", dwInterleaveEvery, hdrNew.dwInitialFrames, hdrNew.dwTotalFrames);
  483. if (dwInterleaveEvery == 1) {
  484. hdrNew.dwFlags |= AVIF_ISINTERLEAVED;
  485. AVIFileEndRecord(pfilesave); // Make first record....
  486. }
  487. //
  488. // Interleaved case: loop from start to end...
  489. //
  490. for (l = - (LONG) hdrNew.dwInitialFrames;
  491. l < (LONG) hdrNew.dwTotalFrames;
  492. l += lInterval) {
  493. //
  494. // Loop through all of the streams to see what needs to be
  495. // done at this time...
  496. //
  497. for (stream = 0; stream < nStreams; stream++) {
  498. LONG lPos;
  499. LONG lPosNext;
  500. LONG lStart;
  501. LONG lEnd;
  502. hr = AVIStreamInfo(apaviNew[stream], &strhdr, sizeof(strhdr));
  503. if (hr != AVIERR_OK)
  504. goto Error;
  505. if (l < - (LONG) strhdr.dwInitialFrames)
  506. continue;
  507. // !!! Better use of TWOCCs...
  508. if (strhdr.fccType == streamtypeAUDIO)
  509. cktype = cktypeWAVEbytes;
  510. else if (strhdr.fccType == streamtypeVIDEO) {
  511. if (strhdr.fccHandler == comptypeDIB)
  512. cktype = cktypeDIBbits;
  513. else
  514. cktype = cktypeDIBcompressed;
  515. } else
  516. cktype = aviTWOCC('x', 'x');
  517. //
  518. // Time is based on the first stream:
  519. // Right now, we want to write out any data in the current
  520. // stream that lines up between time <l> and <l+1> in the
  521. // first stream.
  522. //
  523. lPos = l + strhdr.dwInitialFrames;
  524. lPosNext = lPos + lInterval;
  525. lStart = lDone[stream];
  526. if (l >= (LONG) hdrNew.dwTotalFrames - lInterval) {
  527. // If this is going to be the last time through the
  528. // interleave loop, make sure everything gets written.
  529. lEnd = AVIStreamEnd(apavi[stream]);
  530. } else {
  531. //
  532. // Complication: to make the audio come in bigger chunks,
  533. // we only write it out every once in a while.
  534. //
  535. if (strhdr.fccType == streamtypeAUDIO && stream != 0) {
  536. if ((lPos % dwInterleaveEvery) != 0)
  537. continue;
  538. lPosNext = lPos + dwInterleaveEvery;
  539. }
  540. if (stream != 0) {
  541. //
  542. // Figure out the data for this stream that needs to be
  543. // written this time....
  544. //
  545. lEnd = AVIStreamSampleToSample(apavi[stream], apavi[0], lPosNext);
  546. } else {
  547. lEnd = min(lPosNext, (LONG) hdrNew.dwTotalFrames);
  548. }
  549. }
  550. lDone[stream] = lEnd;
  551. //
  552. // Loop until we've read all we want.
  553. //
  554. while (lEnd > lStart) {
  555. // !!! Right here, we should call AVIStreamGetFormat
  556. // and then call AVIStreamSetFormat on the new
  557. // streams.
  558. // !!! Whose job is it to tell if the format has really
  559. // changed?
  560. cbFormat = dwBufferSize;
  561. hr = AVIStreamReadFormat(apavi[stream],
  562. lStart,
  563. lpBuffer,
  564. &cbFormat);
  565. if (hr != AVIERR_OK) {
  566. DPF("AVIStreamReadFormat failed!\n");
  567. goto Error;
  568. }
  569. hr = AVIStreamSetFormat(apaviNew[stream],
  570. lStart,
  571. lpBuffer,
  572. cbFormat);
  573. if (hr != AVIERR_OK) {
  574. // !!! Oh, well: we couldn't write the palette change...
  575. DPF("AVIStreamSetFormat failed!\n");
  576. }
  577. ReadAgain0:
  578. cbFormat = dwBufferSize;
  579. dwSamplesRead = 0;
  580. hr = AVIStreamRead(apavi[stream], lStart,
  581. lEnd - lStart,
  582. lpBuffer, dwBufferSize,
  583. &dwSize, &dwSamplesRead);
  584. if (// dwSamplesRead == 0 &&
  585. (GetScode(hr) == AVIERR_BUFFERTOOSMALL)) {
  586. //
  587. // The frame didn't fit in our buffer.
  588. // Make a bigger buffer.
  589. //
  590. dwBufferSize *= 2;
  591. DPF("Resizing buffer to be %lx bytes\n", dwBufferSize);
  592. lpBuffer = GlobalReAllocPtr(lpBuffer, dwBufferSize, GMEM_MOVEABLE);
  593. if (lpBuffer)
  594. goto ReadAgain0;
  595. hr = ResultFromScode(AVIERR_MEMORY);
  596. }
  597. if (hr != 0) {
  598. DPF("AVISave: Error %08lx reading stream %d, position %ld!\n", (DWORD) hr, stream, lStart);
  599. goto Error;
  600. }
  601. dwFlags = 0;
  602. if (AVIStreamFindSample(apavi[stream], lStart,
  603. FIND_KEY | FIND_PREV) == lStart)
  604. dwFlags |= AVIIF_KEYFRAME;
  605. hr = AVIStreamWrite(apaviNew[stream],
  606. -1, dwSamplesRead,
  607. lpBuffer, dwSize,
  608. // cktype, // !!!
  609. dwFlags, 0L, 0L);
  610. if (hr != AVIERR_OK)
  611. goto Error;
  612. lStart += dwSamplesRead;
  613. }
  614. }
  615. //
  616. // Mark the end of the frame, in case we're writing out
  617. // the "strict" interleaved format with LIST 'rec' chunks...
  618. //
  619. if (dwInterleaveEvery == 1) {
  620. hr = AVIFileEndRecord(pfilesave);
  621. if (hr != AVIERR_OK) {
  622. DPF("AVISave: Error from EndRecord!\n");
  623. goto Error;
  624. }
  625. }
  626. // Give the application a chance to update status and the user
  627. // a chance to abort...
  628. if (lpfnCallback((int)
  629. muldiv32(l + hdrNew.dwInitialFrames, 100,
  630. hdrNew.dwInitialFrames +
  631. hdrNew.dwTotalFrames))) {
  632. hr = ResultFromScode(AVIERR_USERABORT);
  633. DPF("AVISave: Aborted!\n");
  634. goto Error;
  635. }
  636. }
  637. } else {
  638. //
  639. // Non-interleaved case: loop through the streams and write
  640. // each one out by itself.
  641. //
  642. DPF("Saving non-interleaved.\n");
  643. for (stream = 0; stream < nStreams; stream++) {
  644. if (lpfnCallback(MulDiv(stream, 100, nStreams))) {
  645. hr = ResultFromScode(AVIERR_USERABORT);
  646. goto Error;
  647. }
  648. AVIStreamInfo(apavi[stream], &strhdr, sizeof(strhdr));
  649. DPF("Saving stream %d: start=%lx, len=%lx\n", stream, strhdr.dwStart, strhdr.dwLength);
  650. // !!! Need better cktype handling....
  651. if (strhdr.fccType == streamtypeAUDIO)
  652. cktype = cktypeWAVEbytes;
  653. else if (strhdr.fccType == streamtypeVIDEO) {
  654. if (strhdr.fccHandler == comptypeDIB)
  655. cktype = cktypeDIBbits;
  656. else
  657. cktype = cktypeDIBcompressed;
  658. } else
  659. cktype = aviTWOCC('x', 'x');
  660. //
  661. // As usual, there are two possibilities:
  662. //
  663. // 1.) "wave-like" data, where lots of samples can be in
  664. // a single chunk. In this case, we write out big chunks
  665. // with many samples at a time.
  666. //
  667. // 2.) "video-like" data, where each sample is a different
  668. // size, and thus each must be written individually.
  669. //
  670. if (strhdr.dwSampleSize != 0) {
  671. /* It's wave-like data: lots of samples per chunk */
  672. l = strhdr.dwStart;
  673. while (l < (LONG) strhdr.dwLength) {
  674. DWORD dwRead;
  675. // Make the format of the new stream
  676. // match the old one at every point....
  677. //
  678. // !!! Whose job is it to tell if the format has really
  679. // changed?
  680. cbFormat = dwBufferSize;
  681. hr = AVIStreamReadFormat(apavi[stream],
  682. l,
  683. lpBuffer,
  684. &cbFormat);
  685. if (hr != AVIERR_OK) {
  686. DPF("AVIStreamReadFormat failed!\n");
  687. goto Error;
  688. }
  689. hr = AVIStreamSetFormat(apaviNew[stream],
  690. l,
  691. lpBuffer,
  692. cbFormat);
  693. if (hr != AVIERR_OK) {
  694. DPF("AVIStreamSetFormat failed!\n");
  695. // !!! Oh, well: we couldn't write the palette change...
  696. }
  697. //
  698. // Read some data...
  699. //
  700. ReadAgain1:
  701. dwSize = dwBufferSize;
  702. dwSamplesRead = 0;
  703. dwRead = min(dwBufferSize / strhdr.dwSampleSize,
  704. strhdr.dwLength - (DWORD) l);
  705. hr = AVIStreamRead(apavi[stream], l, dwRead,
  706. lpBuffer, dwBufferSize,
  707. &dwSize, &dwSamplesRead);
  708. if (dwSamplesRead == 0 &&
  709. (GetScode(hr) == AVIERR_BUFFERTOOSMALL)) {
  710. //
  711. // The frame didn't fit in our buffer.
  712. // Make a bigger buffer.
  713. //
  714. dwBufferSize *= 2;
  715. lpBuffer = GlobalReAllocPtr(lpBuffer, dwBufferSize, GMEM_MOVEABLE);
  716. if (lpBuffer)
  717. goto ReadAgain1;
  718. }
  719. // !!! Check if format has changed
  720. dwFlags = 0; // !!! KEYFRAME?
  721. DPF("Save: Read %lx/%lx samples at %lx\n", dwSamplesRead, dwRead, l);
  722. if (hr != AVIERR_OK) {
  723. DPF("Save: Read failed! (%08lx) pos=%lx, len=%lx\n", (DWORD) hr, l, dwRead);
  724. goto Error;
  725. }
  726. if (dwSamplesRead == 0) {
  727. DPF("Ack: Read zero samples!");
  728. if (l + 1 == (LONG) strhdr.dwLength) {
  729. DPF("Pretending it's OK, since this was the last one....");
  730. break;
  731. }
  732. hr = ResultFromScode(AVIERR_FILEREAD);
  733. goto Error;
  734. }
  735. l += dwSamplesRead;
  736. //
  737. // Write the data out...
  738. //
  739. hr = AVIStreamWrite(apaviNew[stream],
  740. -1, dwSamplesRead,
  741. lpBuffer, dwSize,
  742. // !!! cktype, // !!!TWOCCFromFOURCC(ckid),
  743. dwFlags, 0L, 0L);
  744. if (hr != AVIERR_OK) {
  745. DPF("AVIStreamWrite failed! (%08lx)\n", (DWORD) hr);
  746. goto Error;
  747. }
  748. if (lpfnCallback(MulDiv(stream, 100, nStreams) +
  749. (int) muldiv32(l, 100,
  750. nStreams * strhdr.dwLength))) {
  751. hr = ResultFromScode(AVIERR_USERABORT);
  752. goto Error;
  753. }
  754. }
  755. } else {
  756. /* It's video-like data: one sample (frame) per chunk */
  757. for (l = strhdr.dwStart;
  758. l < (LONG) strhdr.dwLength;
  759. l++) {
  760. // !!! Right here, we should call AVIStreamGetFormat
  761. // and then call AVIStreamSetFormat on the new
  762. // streams.
  763. // !!! Whose job is it to tell if the format has really
  764. // changed?
  765. cbFormat = dwBufferSize;
  766. hr = AVIStreamReadFormat(apavi[stream],
  767. l,
  768. lpBuffer,
  769. &cbFormat);
  770. if (hr != AVIERR_OK) {
  771. DPF("AVIStreamReadFormat failed!\n");
  772. goto Error;
  773. }
  774. hr = AVIStreamSetFormat(apaviNew[stream],
  775. l,
  776. lpBuffer,
  777. cbFormat);
  778. if (hr != AVIERR_OK) {
  779. // !!! Oh, well: we couldn't write the palette change...
  780. DPF("AVIStreamSetFormat failed!\n");
  781. }
  782. ReadAgain:
  783. dwSize = dwBufferSize;
  784. /* Write out a single frame.... */
  785. dwSamplesRead = 0;
  786. hr = AVIStreamRead(apavi[stream], l, 1,
  787. lpBuffer, dwBufferSize,
  788. &dwSize, &dwSamplesRead);
  789. // !!! Check if format has changed (palette change)
  790. if (dwSamplesRead == 0 &&
  791. (GetScode(hr) == AVIERR_BUFFERTOOSMALL)) {
  792. //
  793. // The frame didn't fit in our buffer.
  794. // Make a bigger buffer.
  795. //
  796. dwBufferSize *= 2;
  797. lpBuffer = GlobalReAllocPtr(lpBuffer, dwBufferSize, GMEM_MOVEABLE);
  798. if (lpBuffer)
  799. goto ReadAgain;
  800. }
  801. if (dwSamplesRead != 1) {
  802. hr = ResultFromScode(AVIERR_FILEREAD);
  803. goto Error;
  804. }
  805. dwFlags = 0; // !!!!
  806. //
  807. // Check whether this should be marked a key frame.
  808. //
  809. // !!! shouldn't this be returned from AVIStreamRead()?
  810. //
  811. if (AVIStreamFindSample(apavi[stream], l,
  812. FIND_KEY | FIND_PREV) == l)
  813. dwFlags |= AVIIF_KEYFRAME;
  814. //
  815. // Write the chunk out.
  816. //
  817. hr = AVIStreamWrite(apaviNew[stream],
  818. -1, dwSamplesRead,
  819. lpBuffer, dwSize,
  820. // !!! cktype, // !!!TWOCCFromFOURCC(ckid),
  821. dwFlags, 0L, 0L);
  822. if (hr != AVIERR_OK)
  823. goto Error;
  824. //
  825. // Video frames can be big, so call back every time.
  826. //
  827. if (lpfnCallback(MulDiv(stream, 100, nStreams) +
  828. (int) muldiv32(l, 100, nStreams * strhdr.dwLength))) {
  829. hr = ResultFromScode(AVIERR_USERABORT);
  830. goto Error;
  831. }
  832. }
  833. }
  834. }
  835. }
  836. Error:
  837. //
  838. // We're done, one way or another.
  839. //
  840. /* Free buffer */
  841. if (lpBuffer) {
  842. GlobalFreePtr(lpBuffer);
  843. }
  844. // If everything's OK so far, finish writing the file.
  845. // Close the file, free resources associated with writing it.
  846. if (pfilesave) {
  847. // Release all of our new streams
  848. for (stream = 0; stream < nStreams; stream++) {
  849. if (apaviNew[stream])
  850. AVIStreamClose(apaviNew[stream]);
  851. }
  852. if (hr != AVIERR_OK)
  853. AVIFileClose(pfilesave);
  854. else {
  855. // !!! ACK: AVIFileClose doesn't return an error! How do I tell
  856. // if it worked?
  857. // !!! does this mean I need a Flush() call?
  858. /* hr = */ AVIFileClose(pfilesave);
  859. }
  860. }
  861. // Release all of our streams
  862. for (stream = 0; stream < nStreams; stream++) {
  863. if (apavi[stream])
  864. AVIStreamClose(apavi[stream]);
  865. }
  866. if (hr != 0) {
  867. DPF("AVISave: Returning error %08lx\n", (DWORD) hr);
  868. }
  869. return hr;
  870. }