Leaked source code of windows server 2003
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.

1259 lines
36 KiB

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