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.

643 lines
18 KiB

  1. /*******************************Module*Header*********************************\
  2. * Module Name: playwav.c
  3. *
  4. * Sound support routines for NT - ported from Windows 3.1 Sonic
  5. *
  6. * Created:
  7. * Author:
  8. * Jan 92: Ported to Win32 - SteveDav
  9. *
  10. * History:
  11. *
  12. * Copyright (c) 1992-1998 Microsoft Corporation
  13. *
  14. \******************************************************************************/
  15. #define UNICODE
  16. #define MMNOSEQ
  17. #define MMNOJOY
  18. #define MMNOMIDI
  19. #define MMNOMCI
  20. #include "winmmi.h"
  21. #include "playwav.h"
  22. //
  23. // These globals are used to keep track of the currently playing sound, and
  24. // the handle to the wave device. only 1 sound can be playing at a time.
  25. //
  26. STATICDT HWAVEOUT hWaveOut; // handle to open wave device
  27. LPWAVEHDR lpWavHdr; // current wave file playing
  28. ULONG timeAbort; // time at which we should give up waiting
  29. // for a playing sound to finish
  30. CRITICAL_SECTION WavHdrCritSec;
  31. #define EnterWavHdr() EnterCriticalSection(&WavHdrCritSec);
  32. #define LeaveWavHdr() LeaveCriticalSection(&WavHdrCritSec);
  33. /* flags for _lseek */
  34. #define SEEK_CUR 1
  35. #define SEEK_END 2
  36. #define SEEK_SET 0
  37. #define FMEM (GMEM_MOVEABLE)
  38. STATICFN BOOL NEAR PASCAL soundInitWavHdr(LPWAVEHDR lpwh, LPBYTE lpMem, DWORD dwLen);
  39. STATICFN BOOL NEAR PASCAL soundOpen(HANDLE hSound, UINT wFlags);
  40. STATICFN BOOL NEAR PASCAL soundClose(void);
  41. STATICFN void NEAR PASCAL soundWait(void);
  42. /*****************************************************************************
  43. * @doc INTERNAL
  44. *
  45. * @api void | WaveOutNotify | called by mmWndProc when it receives a
  46. * MM_WOM_DONE message
  47. * @rdesc None.
  48. *
  49. ****************************************************************************/
  50. void FAR PASCAL WaveOutNotify(
  51. DWORD wParam,
  52. LONG lParam)
  53. {
  54. EnterWavHdr();
  55. #if DBG
  56. WinAssert(!hWaveOut || lpWavHdr); // if hWaveOut, then MUST have lpWavHdr
  57. #endif
  58. if (hWaveOut && !(lpWavHdr->dwFlags & WHDR_DONE)) {
  59. LeaveWavHdr();
  60. return; // wave is not done! get out
  61. }
  62. LeaveWavHdr();
  63. //
  64. // wave file is done! release the device
  65. //
  66. dprintf2(("ASYNC sound done, closing wave device"));
  67. soundClose();
  68. }
  69. /*****************************************************************************
  70. * @doc INTERNAL
  71. *
  72. * @api BOOL | soundPlay | Pretty much speaks for itself!
  73. *
  74. * @parm HANDLE | hSound | The sound resource to play.
  75. *
  76. * @parm wFlags | UINT | flags controlling sync/async etc.
  77. *
  78. * @flag SND_SYNC | play synchronously (default)
  79. * @flag SND_ASYNC | play asynchronously
  80. *
  81. * @rdesc Returns TRUE if successful and FALSE on failure.
  82. ****************************************************************************/
  83. BOOL NEAR PASCAL soundPlay(
  84. HANDLE hSound,
  85. UINT wFlags)
  86. {
  87. //
  88. // Before playing a sound release it
  89. //
  90. soundClose();
  91. //
  92. // If the current session is disconnected
  93. // then don't bother playing
  94. //
  95. if (WTSCurrentSessionIsDisconnected()) return TRUE;
  96. //
  97. // open the sound device and write the sound to it.
  98. //
  99. if (!soundOpen(hSound, wFlags)) {
  100. dprintf1(("Returning false after calling SoundOpen"));
  101. return FALSE;
  102. }
  103. dprintf2(("SoundOpen OK"));
  104. if (!(wFlags & SND_ASYNC))
  105. {
  106. dprintf4(("Calling SoundWait"));
  107. soundWait();
  108. dprintf4(("Calling SoundClose"));
  109. soundClose();
  110. }
  111. return TRUE;
  112. }
  113. /*****************************************************************************
  114. * @doc INTERNAL
  115. *
  116. * @api BOOL | soundOpen | Open the wave device and write a sound to it.
  117. *
  118. * @parm HANDLE | hSound | The sound resource to play.
  119. *
  120. * @rdesc Returns TRUE if successful and FALSE on failure.
  121. ****************************************************************************/
  122. STATICFN BOOL NEAR PASCAL soundOpen(
  123. HANDLE hSound,
  124. UINT wFlags)
  125. {
  126. UINT wErr;
  127. DWORD flags = WAVE_ALLOWSYNC;
  128. BOOL fResult = FALSE;
  129. if (!hSound) {
  130. return FALSE;
  131. }
  132. if (hWaveOut)
  133. {
  134. dprintf1(("WINMM: soundOpen() wave device is currently open."));
  135. return FALSE;
  136. }
  137. try {
  138. EnterWavHdr();
  139. lpWavHdr = (LPWAVEHDR)GlobalLock(hSound);
  140. if (!lpWavHdr)
  141. {
  142. #if DBG
  143. if ((GlobalFlags(hSound) & GMEM_DISCARDED)) {
  144. dprintf1(("WINMM: sound was discarded before play could begin."));
  145. }
  146. #endif
  147. goto exit;
  148. }
  149. //
  150. // open the wave device, open any wave device that supports the
  151. // format
  152. //
  153. if (hwndNotify) {
  154. flags |= CALLBACK_WINDOW;
  155. }
  156. wErr = waveOutOpen(&hWaveOut, // returns handle to device
  157. (UINT)WAVE_MAPPER, // device id (any device)
  158. (LPWAVEFORMATEX)lpWavHdr->dwUser, // wave format
  159. (DWORD_PTR)hwndNotify, // callback function
  160. 0L, // callback instance data
  161. flags); // flags
  162. if (wErr != 0)
  163. {
  164. dprintf1(("WINMM: soundOpen() unable to open wave device"));
  165. GlobalUnlock(hSound);
  166. hWaveOut = NULL;
  167. lpWavHdr = NULL;
  168. goto exit;
  169. }
  170. wErr = waveOutPrepareHeader(hWaveOut, lpWavHdr, sizeof(WAVEHDR));
  171. if (wErr != 0)
  172. {
  173. dprintf1(("WINMM: soundOpen() waveOutPrepare failed"));
  174. soundClose();
  175. goto exit;
  176. }
  177. //
  178. // Only allow sound looping if playing ASYNC sounds
  179. //
  180. if ((wFlags & SND_ASYNC) && (wFlags & SND_LOOP))
  181. {
  182. lpWavHdr->dwLoops = 0xFFFFFFFF; // infinite loop
  183. lpWavHdr->dwFlags |= WHDR_BEGINLOOP|WHDR_ENDLOOP;
  184. }
  185. else
  186. {
  187. lpWavHdr->dwLoops = 0;
  188. lpWavHdr->dwFlags &=~(WHDR_BEGINLOOP|WHDR_ENDLOOP);
  189. }
  190. lpWavHdr->dwFlags &= ~WHDR_DONE; // mark as not done!
  191. wErr = waveOutWrite(hWaveOut, lpWavHdr, sizeof(WAVEHDR));
  192. timeAbort = lpWavHdr->dwBufferLength * 1000 / ((LPWAVEFORMATEX)lpWavHdr->dwUser)->nAvgBytesPerSec;
  193. timeAbort = timeAbort * 2; // 100% room for slew between audio and system clocks
  194. timeAbort = timeAbort + timeGetTime();
  195. if (wErr != 0)
  196. {
  197. dprintf1(("WINMM: soundOpen() waveOutWrite failed"));
  198. soundClose();
  199. goto exit;
  200. }
  201. fResult = TRUE;
  202. exit: ;
  203. } finally {
  204. LeaveWavHdr();
  205. }
  206. return fResult;
  207. }
  208. /*****************************************************************************
  209. * @doc INTERNAL
  210. *
  211. * @func BOOL | soundClose | This function closes the sound device
  212. *
  213. * @rdesc Returns TRUE if successful and FALSE on failure.
  214. ****************************************************************************/
  215. STATICFN BOOL NEAR PASCAL soundClose(
  216. void)
  217. {
  218. UINT wErr;
  219. //
  220. // Do we have the sound device open?
  221. //
  222. try {
  223. EnterWavHdr();
  224. if (!lpWavHdr || !hWaveOut) {
  225. // return TRUE;
  226. } else {
  227. //
  228. // if the block is still playing, stop it!
  229. //
  230. if (!(lpWavHdr->dwFlags & WHDR_DONE)) {
  231. waveOutReset(hWaveOut);
  232. }
  233. #if DBG
  234. if (!(lpWavHdr->dwFlags & WHDR_DONE))
  235. {
  236. dprintf1(("WINMM: soundClose() data is not DONE!???"));
  237. lpWavHdr->dwFlags |= WHDR_DONE;
  238. }
  239. if (!(lpWavHdr->dwFlags & WHDR_PREPARED))
  240. {
  241. dprintf1(("WINMM: soundClose() data not prepared???"));
  242. }
  243. #endif
  244. //
  245. // unprepare the data anyway!
  246. //
  247. wErr = waveOutUnprepareHeader(hWaveOut, lpWavHdr, sizeof(WAVEHDR));
  248. if (wErr != 0)
  249. {
  250. dprintf1(("WINMM: soundClose() waveOutUnprepare failed!"));
  251. }
  252. //
  253. // finally, actually close the device, and unlock the data
  254. //
  255. waveOutClose(hWaveOut);
  256. GlobalUnlock(GlobalHandle(lpWavHdr));
  257. //
  258. // update globals, claiming the device is closed.
  259. //
  260. hWaveOut = NULL;
  261. lpWavHdr = NULL;
  262. }
  263. } finally {
  264. LeaveWavHdr();
  265. }
  266. return TRUE;
  267. }
  268. /*****************************************************************************
  269. * @doc INTERNAL
  270. *
  271. * @api void | soundWait | wait for the sound device to complete
  272. *
  273. * @rdesc none
  274. ****************************************************************************/
  275. STATICFN void NEAR PASCAL soundWait(
  276. void)
  277. {
  278. try { // This should ensure that even WOW
  279. // threads that die on us depart the
  280. // critical section
  281. EnterWavHdr();
  282. if (lpWavHdr) {
  283. LPWAVEHDR lpExisting; // current playing wave file
  284. lpExisting = lpWavHdr;
  285. while (lpExisting == lpWavHdr &&
  286. !(lpWavHdr->dwFlags & WHDR_DONE) &&
  287. (timeGetTime() < timeAbort)
  288. )
  289. {
  290. dprintf4(("Waiting for buffer to complete"));
  291. LeaveWavHdr();
  292. Sleep(75);
  293. EnterWavHdr();
  294. // LATER !! We should have an event (on another thread... sigh...)
  295. // which will be triggered when the buffer is played. Waiting
  296. // on the WHDR_DONE bit is ported directly from Win 3.1 and is
  297. // certainly not the best way of doing this. The disadvantage of
  298. // using the thread notification is signalling this thread to
  299. // continue.
  300. }
  301. }
  302. } finally {
  303. LeaveWavHdr();
  304. }
  305. }
  306. /*****************************************************************************
  307. * @doc INTERNAL
  308. *
  309. * @api void | soundFree | This function frees a sound resource created
  310. * with soundLoadFile or soundLoadMemory
  311. *
  312. * @rdesc Returns TRUE if successful and FALSE on failure.
  313. ****************************************************************************/
  314. void NEAR PASCAL soundFree(
  315. HANDLE hSound)
  316. {
  317. // Allow a null handle to stop any pending sounds, without discarding
  318. // the current cached sound
  319. //
  320. // !!! we should only close the sound device iff this hSound is playing!
  321. //
  322. soundClose();
  323. if (hSound) {
  324. GlobalFree(hSound);
  325. }
  326. }
  327. /*****************************************************************************
  328. * @doc INTERNAL
  329. *
  330. * @api HANDLE | soundLoadFile | Loads a specified sound resource from a
  331. * file into a global, discardable object.
  332. *
  333. * @parm LPCSTR | lpszFile | The file from which to load the sound resource.
  334. *
  335. * @rdesc Returns NULL on failure, GLOBAL HANDLE to a WAVEHDR iff success
  336. ****************************************************************************/
  337. HANDLE NEAR PASCAL soundLoadFile(
  338. LPCWSTR szFileName)
  339. {
  340. HANDLE fh;
  341. DWORD dwSize;
  342. LPBYTE lpData;
  343. HANDLE h;
  344. UINT wNameLen;
  345. // open the file
  346. fh = CreateFile( szFileName,
  347. GENERIC_READ,
  348. FILE_SHARE_READ | FILE_SHARE_WRITE,
  349. NULL,
  350. OPEN_EXISTING,
  351. FILE_ATTRIBUTE_NORMAL,
  352. NULL );
  353. if (fh == (HANDLE)(UINT_PTR)HFILE_ERROR) {
  354. dprintf3(("soundLoadFile: Failed to open %ls Error is %d",szFileName, GetLastError()));
  355. return NULL;
  356. } else {
  357. dprintf3(("soundLoadFile: opened %ls",szFileName));
  358. }
  359. /* Get wNameLen rounded up to next WORD boundary.
  360. * We do not need to round up to a DWORD boundary as this value is
  361. * about to be multiplied by sizeof(WCHAR) which will do the additional
  362. * boundary alignment for us. If we ever contemplate moving back to
  363. * non-UNICODE then this statement will have to be changed. The
  364. * alignment is needed so that the actual wave data starts on a
  365. * DWORD boundary.
  366. */
  367. wNameLen = ((lstrlen(szFileName) + 1 + sizeof(WORD) - 1) /
  368. sizeof(WORD)) * sizeof(WORD);
  369. #define BLOCKBYTES (sizeof(SOUNDFILE) + (wNameLen * sizeof(WCHAR)))
  370. // The amount of space we need to allocate - the WAVEHDR, file size, date
  371. // time plus the file name and a terminating null.
  372. dwSize = GetFileSize(fh, NULL);
  373. // note: could also use the C function FILELENGTH
  374. if (HFILE_ERROR == dwSize) {
  375. dprintf2(("Failed to find file size: %ls", szFileName));
  376. goto error1;
  377. }
  378. // allocate some discardable memory for a wave hdr, name and the file data.
  379. h = GlobalAlloc( FMEM + GMEM_DISCARDABLE,
  380. BLOCKBYTES + dwSize );
  381. if (!h) {
  382. dprintf3(("soundLoadFile: Failed to allocate memory"));
  383. goto error1;
  384. }
  385. // lock it down
  386. if (NULL == (lpData = GlobalLock(h))) goto error2;
  387. // read the file into the memory block
  388. // NOTE: We could, and probably should, use the file mapping functions.
  389. // Do this LATER
  390. if ( _lread( (HFILE)(DWORD_PTR)fh,
  391. lpData + BLOCKBYTES,
  392. (UINT)dwSize)
  393. != dwSize ) {
  394. goto error3;
  395. }
  396. // Save the last written time, and the file size
  397. ((PSOUNDFILE)lpData)->Size = dwSize;
  398. GetFileTime(fh, NULL, NULL, &(((PSOUNDFILE)lpData)->ft));
  399. // do the rest of it from the memory image
  400. //
  401. // MIPS WARNING !! Unaligned data - wNameLen is arbitrary
  402. //
  403. if (!soundInitWavHdr( (LPWAVEHDR)lpData,
  404. lpData + BLOCKBYTES,
  405. dwSize) )
  406. {
  407. dprintf3(("soundLoadFile: Failed to InitWaveHdr"));
  408. goto error3;
  409. }
  410. CloseHandle(fh);
  411. lstrcpyW( ((PSOUNDFILE)lpData)->Filename, szFileName);
  412. GlobalUnlock(h);
  413. return h;
  414. error3:
  415. GlobalUnlock(h);
  416. error2:
  417. GlobalFree(h);
  418. error1:
  419. CloseHandle(fh);
  420. return NULL;
  421. }
  422. /*****************************************************************************
  423. * @doc INTERNAL
  424. *
  425. * @api HANDLE | soundLoadMemory | Loads a user specified sound resource from a
  426. * a memory block supplied by the caller.
  427. *
  428. * @parm LPCSTR | lpMem | Pointer to a memory image of the file
  429. *
  430. * @rdesc Returns NULL on failure, GLOBAL HANDLE to a WAVEHDR iff success
  431. ****************************************************************************/
  432. HANDLE NEAR PASCAL soundLoadMemory(
  433. LPBYTE lpMem)
  434. {
  435. HANDLE h;
  436. LPBYTE lp;
  437. // allocate some memory, for a wave hdr
  438. h = GlobalAlloc(FMEM, (LONG)(sizeof(SOUNDFILE) + sizeof(WCHAR)) );
  439. if (!h) {
  440. goto error1;
  441. }
  442. // lock it down
  443. if (NULL == (lp = GlobalLock(h))) goto error2;
  444. //
  445. // we must assume the memory pointer is correct! (hence the -1l)
  446. //
  447. if (!soundInitWavHdr( (LPWAVEHDR)lp, lpMem, (DWORD)-1l)) {
  448. goto error3;
  449. }
  450. //*(LPWSTR)(lp + sizeof(WAVEHDR)+sizeof(SOUNDFILE)) = '\0'; // No file name for memory file
  451. ((PSOUNDFILE)lp)->Filename[0] = '\0'; // No file name for memory file
  452. ((PSOUNDFILE)lp)->Size = 0;
  453. GlobalUnlock(h);
  454. return h;
  455. error3:
  456. GlobalUnlock(h);
  457. error2:
  458. GlobalFree(h);
  459. error1:
  460. return NULL;
  461. }
  462. /*****************************************************************************
  463. * @doc INTERNAL
  464. *
  465. * @api BOOL | soundInitWavHdr | Initializes a WAVEHDR data structure from a
  466. * pointer to a memory image of a RIFF WAV file.
  467. *
  468. * @parm LPWAVEHDR | lpwh | Pointer to a WAVEHDR
  469. *
  470. * @parm LPCSTR | lpMem | Pointer to a memory image of a RIFF WAV file
  471. *
  472. * @rdesc Returns FALSE on failure, TRUE on success.
  473. *
  474. * @comm the dwUser field of the WAVEHDR structure is initialized to point
  475. * to the WAVEFORMAT structure that is inside the RIFF data
  476. *
  477. ****************************************************************************/
  478. STATICFN BOOL NEAR PASCAL soundInitWavHdr(
  479. LPWAVEHDR lpwh,
  480. LPBYTE lpMem,
  481. DWORD dwLen)
  482. {
  483. FPFileHeader fpHead;
  484. LPWAVEFORMAT lpFmt;
  485. LPBYTE lpData;
  486. DWORD dwFileSize,dwCurPos;
  487. DWORD dwSize;
  488. DWORD AlignError;
  489. DWORD FmtSize;
  490. if (dwLen < sizeof(FileHeader)) {
  491. dprintf3(("Not a RIFF file, or not a WAVE file"));
  492. return FALSE;
  493. }
  494. // assume the first few bytes are the file header
  495. fpHead = (FPFileHeader) lpMem;
  496. // check that it's a valid RIFF file and a valid WAVE form.
  497. if (fpHead->dwRiff != RIFF_FILE || fpHead->dwWave != RIFF_WAVE ) {
  498. return FALSE;
  499. }
  500. dwFileSize = fpHead->dwSize;
  501. dwCurPos = sizeof(FileHeader);
  502. lpData = lpMem + sizeof(FileHeader);
  503. if (dwLen < dwFileSize) { // RIFF header
  504. return FALSE;
  505. }
  506. // scan until we find the 'fmt' chunk
  507. while( 1 ) {
  508. if( ((FPChunkHeader)lpData)->dwCKID == RIFF_FORMAT ) {
  509. break; // from the while loop that's looking for it
  510. }
  511. dwCurPos += ((FPChunkHeader)lpData)->dwSize + sizeof(ChunkHeader);
  512. if( dwCurPos >= dwFileSize ) {
  513. return FALSE;
  514. }
  515. lpData += ((FPChunkHeader)lpData)->dwSize + sizeof(ChunkHeader);
  516. }
  517. // now we're at the beginning of the 'fmt' chunk data
  518. lpFmt = (LPWAVEFORMAT) (lpData + sizeof(ChunkHeader));
  519. // Save the size of the format data and check it.
  520. FmtSize = ((FPChunkHeader)lpData)->dwSize;
  521. if (FmtSize < sizeof(WAVEFORMAT)) {
  522. return FALSE;
  523. }
  524. // scan until we find the 'data' chunk
  525. lpData = lpData + ((FPChunkHeader)lpData)->dwSize + sizeof(ChunkHeader);
  526. while( 1 ) {
  527. if ( ((FPChunkHeader)lpData)->dwCKID == RIFF_CHANNEL) {
  528. break; // from the while loop that's looking for it
  529. }
  530. dwCurPos += ((FPChunkHeader)lpData)->dwSize + sizeof(ChunkHeader);
  531. if( dwCurPos >= dwFileSize ) {
  532. return 0;
  533. }
  534. lpData += ((FPChunkHeader)lpData)->dwSize + sizeof(ChunkHeader);
  535. }
  536. //
  537. // The format chunk must be aligned so move things if necessary
  538. // Warning - this is a hack to get round alignment problems
  539. //
  540. AlignError = ((DWORD)((LPBYTE)lpFmt - lpMem)) % sizeof(DWORD);
  541. if (AlignError != 0) {
  542. lpFmt = (LPWAVEFORMAT)((LPBYTE)lpFmt - AlignError);
  543. MoveMemory(lpFmt, (LPBYTE)lpFmt + AlignError, FmtSize);
  544. }
  545. // now we're at the beginning of the 'data' chunk data
  546. dwSize = ((FPChunkHeader)lpData)->dwSize;
  547. lpData = lpData + sizeof(ChunkHeader);
  548. // initialize the WAVEHDR
  549. lpwh->lpData = (LPSTR)lpData; // pointer to locked data buffer
  550. lpwh->dwBufferLength = dwSize; // length of data buffer
  551. lpwh->dwUser = (DWORD_PTR)lpFmt; // for client's use
  552. lpwh->dwFlags = WHDR_DONE; // assorted flags (see defines)
  553. lpwh->dwLoops = 0;
  554. return TRUE;
  555. }