4 * Implementation of sound classes for Win32
6 * Portable Windows Library
8 * Copyright (c) 1993-1998 Equivalence Pty. Ltd.
10 * The contents of this file are subject to the Mozilla Public License
11 * Version 1.0 (the "License"); you may not use this file except in
12 * compliance with the License. You may obtain a copy of the License at
13 * http://www.mozilla.org/MPL/
15 * Software distributed under the License is distributed on an "AS IS"
16 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
17 * the License for the specific language governing rights and limitations
20 * The Original Code is Portable Windows Library.
22 * The Initial Developer of the Original Code is Equivalence Pty. Ltd.
24 * Portions are Copyright (C) 1993 Free Software Foundation, Inc.
25 * All Rights Reserved.
27 * Contributor(s): ______________________________________.
30 * Revision 1.18 2006/07/31 12:08:28 rjongbloed
31 * Fixed problem with WAV file asynchronous play back
33 * Revision 1.17 2006/06/21 04:20:07 csoutheren
36 * Revision 1.16 2006/04/09 11:03:59 csoutheren
37 * Remove warnings on VS.net 2005
39 * Revision 1.15 2005/11/30 12:47:42 csoutheren
40 * Removed tabs, reformatted some code, and changed tags for Doxygen
42 * Revision 1.14 2005/10/06 08:14:55 csoutheren
43 * Fixed race condition in sound driver when shutting down with driver that is not open
45 * Revision 1.13 2005/09/18 13:01:43 dominance
46 * fixed pragma warnings when building with gcc.
48 * Revision 1.12 2005/07/03 13:48:58 shorne
49 * Add the ability to play sound to specified device.
51 * Revision 1.11 2005/04/21 05:27:04 csoutheren
52 * Prevent weird deadlocks when using record-only or play-only sound channels
54 * Revision 1.10 2005/01/04 07:44:04 csoutheren
55 * More changes to implement the new configuration methodology, and also to
56 * attack the global static problem
58 * Revision 1.9 2004/10/23 11:16:17 ykiryanov
59 * Added ifdef _WIN32_WCE for PocketPC 2003 SDK port
61 * Revision 1.8 2004/08/16 06:41:00 csoutheren
62 * Added adapters template to make device plugins available via the abstract factory interface
64 * Revision 1.7 2004/04/09 06:52:18 rjongbloed
65 * Removed #pargma linker command for /delayload of DLL as documentations sais that
68 * Revision 1.6 2004/02/23 23:52:20 csoutheren
69 * Added pragmas to avoid every Windows application needing to include libs explicitly
71 * Revision 1.5 2004/02/15 03:59:20 rjongbloed
72 * Fixed the default number of buffer to be the value determined emprirically in
73 * OpenH323, thanks Ted Szoczei
75 * Revision 1.4 2003/12/29 03:29:26 csoutheren
76 * Allowed access to Windows sound channel declaration, just in case it is required
78 * Revision 1.3 2003/12/29 02:00:40 csoutheren
79 * Moved some declarations to sound_win32.h to allow access
81 * Revision 1.2 2003/11/18 10:50:44 csoutheren
82 * Changed name of Windows sound device
84 * Revision 1.1 2003/11/12 04:39:56 csoutheren
85 * Changed to work with new plugin system
87 * Revision 1.37 2003/11/05 05:57:58 csoutheren
88 * Added #pragma to include required libs
90 * Revision 1.36 2003/09/17 05:45:10 csoutheren
91 * Removed recursive includes
93 * Revision 1.35 2003/06/05 05:20:35 rjongbloed
94 * Fixed WinCE compatibility, thanks Yuri Kiryanov
96 * Revision 1.34 2003/05/29 08:57:38 rjongbloed
97 * Futher changes to not alter balance when changing volume setting, also fixed
98 * correct return of volume level if balance not centred, thanks Diego Tártara
100 * Revision 1.33 2003/05/01 00:17:40 robertj
101 * Fixed setting of stereo volume levels, thanks Diego Tártara
103 * Revision 1.32 2002/08/05 01:22:59 robertj
104 * Fixed possible range error on SetVolume(), thanks Sonya Cooper-Hull
106 * Revision 1.31 2002/02/08 09:59:45 robertj
107 * Slight adjustment to API and documentation for volume functions.
108 * Added implementation for volume function on play, still needs recording.
110 * Revision 1.30 2002/02/07 20:57:21 dereks
111 * add SetVolume and GetVolume methods to PSoundChannel
113 * Revision 1.29 2001/10/23 02:49:48 robertj
114 * Fixed problem with Abort() not always breaking I/O blocked threads.
116 * Revision 1.28 2001/10/12 03:50:27 robertj
117 * Fixed race condition on using Abort() which Reading from another thread.
118 * Fixed failure to start recording if called WaitForXXXFull() functions.
120 * Revision 1.27 2001/10/10 03:29:34 yurik
121 * Added open with format other than PCM
123 * Revision 1.26 2001/09/22 03:36:56 yurik
124 * Put code to prevent audio channel disconnection
126 * Revision 1.25 2001/09/10 02:51:23 robertj
127 * Major change to fix problem with error codes being corrupted in a
128 * PChannel when have simultaneous reads and writes in threads.
130 * Revision 1.24 2001/09/10 02:48:51 robertj
131 * Removed previous change as breaks semantics of Read() function, moved test
132 * for zero buffer length to part that waits for buffer to be full.
134 * Revision 1.23 2001/09/09 17:37:49 yurik
135 * dwBytesRecorded in WAVEHDR could return 0. We should not close the channel in this case
137 * Revision 1.22 2001/09/09 02:17:11 yurik
140 * Revision 1.20 2001/07/01 02:45:01 yurik
141 * WinCE compiler wants implicit cast to format
143 * Revision 1.19 2001/05/04 09:38:07 robertj
144 * Fixed problem with some WAV files having small WAVEFORMATEX chunk.
146 * Revision 1.18 2001/04/10 00:51:11 robertj
147 * Fixed bug in using incorrect function to delete event handle, thanks Victor H.
149 * Revision 1.17 2001/03/15 23:39:29 robertj
150 * Fixed bug with trying to write block larger than one buffer, thanks Norbert Oertel
152 * Revision 1.16 2001/02/07 04:45:54 robertj
153 * Added functions to get current sound channel format parameters.
155 * Revision 1.15 2000/07/04 04:30:47 robertj
156 * Fixed shutdown issues with buffers in use, again.
158 * Revision 1.14 2000/07/01 09:39:31 robertj
159 * Fixed shutdown issues with buffers in use.
161 * Revision 1.13 2000/06/29 00:39:29 robertj
162 * Fixed bug when PWaveFormat is assigned to itself.
164 * Revision 1.12 2000/05/22 07:17:50 robertj
165 * Fixed missing initialisation of format data block in Win32 PSound::Load().
167 * Revision 1.11 2000/05/01 05:59:11 robertj
168 * Added mutex to PSoundChannel buffer structure.
170 * Revision 1.10 2000/03/04 10:15:32 robertj
171 * Added simple play functions for sound files.
173 * Revision 1.9 2000/02/17 11:33:33 robertj
174 * Changed PSoundChannel::Write so blocks instead of error if no buffers available.
176 * Revision 1.8 1999/10/09 01:22:07 robertj
177 * Fixed error display for sound channels.
179 * Revision 1.7 1999/09/23 04:28:44 robertj
180 * Allowed some Win32 only access to wave format in sound channel
182 * Revision 1.6 1999/07/08 08:39:53 robertj
183 * Fixed bug when breaking block by closing the PSoundChannel in other thread.
185 * Revision 1.5 1999/06/24 14:01:25 robertj
186 * Fixed bug in not returning correct default recorder (waveIn) device.
188 * Revision 1.4 1999/06/07 01:36:28 robertj
189 * Fixed incorrect;ly set block alignment in sound structure.
191 * Revision 1.3 1999/05/28 14:04:51 robertj
192 * Added function to get default audio device.
194 * Revision 1.2 1999/02/22 10:15:15 robertj
195 * Sound driver interface implementation to Linux OSS specification.
197 * Revision 1.1 1999/02/16 06:02:07 robertj
198 * Major implementation to Linux OSS model
202 #define P_FORCE_STATIC_PLUGIN
205 #include <ptlib/sound.h>
207 #if defined(_WIN32) && !defined(P_FORCE_STATIC_PLUGIN)
208 #error "sound_win32.cxx must be compiled without precompiled headers"
213 #include <ptlib/plugin.h>
214 #include <ptlib/msos/ptlib/sound_win32.h>
218 #pragma comment(lib, "winmm.lib")
224 PCREATE_SOUND_PLUGIN(WindowsMultimedia
, PSoundChannelWin32
);
226 class PMultiMediaFile
232 BOOL
CreateWaveFile(const PFilePath
& filename
,
233 const PWaveFormat
& waveFormat
,
235 BOOL
OpenWaveFile(const PFilePath
& filename
,
236 PWaveFormat
& waveFormat
,
239 BOOL
Open(const PFilePath
& filename
, DWORD dwOpenFlags
, LPMMIOINFO lpmmioinfo
= NULL
);
240 BOOL
Close(UINT wFlags
= 0);
241 BOOL
Ascend(MMCKINFO
& ckinfo
, UINT wFlags
= 0);
242 BOOL
Descend(UINT wFlags
, MMCKINFO
& ckinfo
, LPMMCKINFO lpckParent
= NULL
);
243 BOOL
Read(void * data
, PINDEX len
);
244 BOOL
CreateChunk(MMCKINFO
& ckinfo
, UINT wFlags
= 0);
245 BOOL
Write(const void * data
, PINDEX len
);
247 DWORD
GetLastError() const { return dwLastError
; }
258 ///////////////////////////////////////////////////////////////////////////////
260 PMultiMediaFile::PMultiMediaFile()
266 PMultiMediaFile::~PMultiMediaFile()
272 BOOL
PMultiMediaFile::CreateWaveFile(const PFilePath
& filename
,
273 const PWaveFormat
& waveFormat
,
276 if (!Open(filename
, MMIO_CREATE
|MMIO_WRITE
))
280 mmChunk
.fccType
= mmioFOURCC('W', 'A', 'V', 'E');
281 mmChunk
.cksize
= 4 + // Form type
282 4 + sizeof(DWORD
) + waveFormat
.GetSize() + // fmt chunk
283 4 + sizeof(DWORD
) + dataSize
; // data chunk
285 // Create a RIFF chunk
286 if (!CreateChunk(mmChunk
, MMIO_CREATERIFF
))
289 // Save the format sub-chunk
290 mmChunk
.ckid
= mmioFOURCC('f', 'm', 't', ' ');
291 mmChunk
.cksize
= waveFormat
.GetSize();
292 if (!CreateChunk(mmChunk
))
295 if (!Write(waveFormat
, waveFormat
.GetSize()))
298 // Save the data sub-chunk
299 mmChunk
.ckid
= mmioFOURCC('d', 'a', 't', 'a');
300 mmChunk
.cksize
= dataSize
;
301 return CreateChunk(mmChunk
);
305 BOOL
PMultiMediaFile::OpenWaveFile(const PFilePath
& filename
,
306 PWaveFormat
& waveFormat
,
310 if (!Open(filename
, MMIO_READ
| MMIO_ALLOCBUF
))
313 MMCKINFO mmParentChunk
, mmSubChunk
;
314 dwLastError
= MMSYSERR_NOERROR
;
316 // Locate a 'RIFF' chunk with a 'WAVE' form type
317 mmParentChunk
.fccType
= mmioFOURCC('W', 'A', 'V', 'E');
318 if (!Descend(MMIO_FINDRIFF
, mmParentChunk
))
321 // Find the format chunk
322 mmSubChunk
.ckid
= mmioFOURCC('f', 'm', 't', ' ');
323 if (!Descend(MMIO_FINDCHUNK
, mmSubChunk
, &mmParentChunk
))
326 // Get the size of the format chunk, allocate memory for it
327 if (!waveFormat
.SetSize(mmSubChunk
.cksize
))
330 // Read the format chunk
331 if (!Read(waveFormat
.GetPointer(), waveFormat
.GetSize()))
334 // Ascend out of the format subchunk
337 // Find the data subchunk
338 mmSubChunk
.ckid
= mmioFOURCC('d', 'a', 't', 'a');
339 if (!Descend(MMIO_FINDCHUNK
, mmSubChunk
, &mmParentChunk
))
342 // Get the size of the data subchunk
343 if (mmSubChunk
.cksize
== 0) {
344 dwLastError
= MMSYSERR_INVALPARAM
;
348 dataSize
= mmSubChunk
.cksize
;
353 BOOL
PMultiMediaFile::Open(const PFilePath
& filename
,
355 LPMMIOINFO lpmmioinfo
)
357 MMIOINFO local_mmioinfo
;
358 if (lpmmioinfo
== NULL
) {
359 lpmmioinfo
= &local_mmioinfo
;
360 memset(lpmmioinfo
, 0, sizeof(local_mmioinfo
));
363 hmmio
= mmioOpen((char *)(const char *)filename
, lpmmioinfo
, dwOpenFlags
);
365 dwLastError
= lpmmioinfo
->wErrorRet
;
367 return hmmio
!= NULL
;
371 BOOL
PMultiMediaFile::Close(UINT wFlags
)
376 mmioClose(hmmio
, wFlags
);
382 BOOL
PMultiMediaFile::Ascend(MMCKINFO
& ckinfo
, UINT wFlags
)
384 dwLastError
= mmioAscend(hmmio
, &ckinfo
, wFlags
);
385 return dwLastError
== MMSYSERR_NOERROR
;
389 BOOL
PMultiMediaFile::Descend(UINT wFlags
, MMCKINFO
& ckinfo
, LPMMCKINFO lpckParent
)
391 dwLastError
= mmioDescend(hmmio
, &ckinfo
, lpckParent
, wFlags
);
392 return dwLastError
== MMSYSERR_NOERROR
;
396 BOOL
PMultiMediaFile::Read(void * data
, PINDEX len
)
398 return mmioRead(hmmio
, (char *)data
, len
) == len
;
402 BOOL
PMultiMediaFile::CreateChunk(MMCKINFO
& ckinfo
, UINT wFlags
)
404 dwLastError
= mmioCreateChunk(hmmio
, &ckinfo
, wFlags
);
405 return dwLastError
== MMSYSERR_NOERROR
;
409 BOOL
PMultiMediaFile::Write(const void * data
, PINDEX len
)
411 return mmioWrite(hmmio
, (char *)data
, len
) == len
;
415 ///////////////////////////////////////////////////////////////////////////////
417 PWaveFormat::PWaveFormat()
424 PWaveFormat::~PWaveFormat()
426 if (waveFormat
!= NULL
)
431 PWaveFormat::PWaveFormat(const PWaveFormat
& fmt
)
434 waveFormat
= (WAVEFORMATEX
*)malloc(size
);
435 PAssert(waveFormat
!= NULL
, POutOfMemory
);
437 memcpy(waveFormat
, fmt
.waveFormat
, size
);
441 PWaveFormat
& PWaveFormat::operator=(const PWaveFormat
& fmt
)
446 if (waveFormat
!= NULL
)
450 waveFormat
= (WAVEFORMATEX
*)malloc(size
);
451 PAssert(waveFormat
!= NULL
, POutOfMemory
);
453 memcpy(waveFormat
, fmt
.waveFormat
, size
);
458 void PWaveFormat::PrintOn(ostream
& out
) const
460 if (waveFormat
== NULL
)
463 out
<< waveFormat
->wFormatTag
<< ','
464 << waveFormat
->nChannels
<< ','
465 << waveFormat
->nSamplesPerSec
<< ','
466 << waveFormat
->nAvgBytesPerSec
<< ','
467 << waveFormat
->nBlockAlign
<< ','
468 << waveFormat
->wBitsPerSample
;
469 if (waveFormat
->cbSize
> 0) {
470 out
<< hex
<< setfill('0');
471 const BYTE
* ptr
= (const BYTE
*)&waveFormat
[1];
472 for (PINDEX i
= 0; i
< waveFormat
->cbSize
; i
++)
473 out
<< ',' << setw(2) << (unsigned)*ptr
++;
474 out
<< dec
<< setfill(' ');
480 void PWaveFormat::ReadFrom(istream
&)
485 void PWaveFormat::SetFormat(unsigned numChannels
,
487 unsigned bitsPerSample
)
489 PAssert(numChannels
== 1 || numChannels
== 2, PInvalidParameter
);
490 PAssert(bitsPerSample
== 8 || bitsPerSample
== 16, PInvalidParameter
);
492 if (waveFormat
!= NULL
)
495 size
= sizeof(WAVEFORMATEX
);
496 waveFormat
= (WAVEFORMATEX
*)malloc(sizeof(WAVEFORMATEX
));
497 PAssert(waveFormat
!= NULL
, POutOfMemory
);
499 waveFormat
->wFormatTag
= WAVE_FORMAT_PCM
;
500 waveFormat
->nChannels
= (WORD
)numChannels
;
501 waveFormat
->nSamplesPerSec
= sampleRate
;
502 waveFormat
->wBitsPerSample
= (WORD
)bitsPerSample
;
503 waveFormat
->nBlockAlign
= (WORD
)(numChannels
*(bitsPerSample
+7)/8);
504 waveFormat
->nAvgBytesPerSec
= waveFormat
->nSamplesPerSec
*waveFormat
->nBlockAlign
;
505 waveFormat
->cbSize
= 0;
509 void PWaveFormat::SetFormat(const void * data
, PINDEX size
)
512 memcpy(waveFormat
, data
, size
);
516 BOOL
PWaveFormat::SetSize(PINDEX sz
)
518 if (waveFormat
!= NULL
)
525 if (sz
< sizeof(WAVEFORMATEX
))
526 sz
= sizeof(WAVEFORMATEX
);
527 waveFormat
= (WAVEFORMATEX
*)calloc(sz
, 1);
528 waveFormat
->cbSize
= (WORD
)(sz
- sizeof(WAVEFORMATEX
));
531 return waveFormat
!= NULL
;
535 ///////////////////////////////////////////////////////////////////////////////
537 PSound::PSound(unsigned channels
,
538 unsigned samplesPerSecond
,
539 unsigned bitsPerSample
,
544 numChannels
= channels
;
545 sampleRate
= samplesPerSecond
;
546 sampleSize
= bitsPerSample
;
549 memcpy(GetPointer(), buffer
, bufferSize
);
553 PSound::PSound(const PFilePath
& filename
)
563 PSound
& PSound::operator=(const PBYTEArray
& data
)
565 PBYTEArray::operator=(data
);
570 void PSound::SetFormat(unsigned channels
,
571 unsigned samplesPerSecond
,
572 unsigned bitsPerSample
)
575 numChannels
= channels
;
576 sampleRate
= samplesPerSecond
;
577 sampleSize
= bitsPerSample
;
578 formatInfo
.SetSize(0);
582 BOOL
PSound::Load(const PFilePath
& filename
)
585 PMultiMediaFile mmio
;
586 PWaveFormat waveFormat
;
588 if (!mmio
.OpenWaveFile(filename
, waveFormat
, dataSize
)) {
589 dwLastError
= mmio
.GetLastError();
593 encoding
= waveFormat
->wFormatTag
;
594 numChannels
= waveFormat
->nChannels
;
595 sampleRate
= waveFormat
->nSamplesPerSec
;
596 sampleSize
= waveFormat
->wBitsPerSample
;
599 PINDEX formatSize
= waveFormat
->cbSize
+ sizeof(WAVEFORMATEX
);
600 memcpy(formatInfo
.GetPointer(formatSize
), waveFormat
, formatSize
);
603 // Allocate and lock memory for the waveform data.
604 if (!SetSize(dataSize
)) {
605 dwLastError
= MMSYSERR_NOMEM
;
609 // Read the waveform data subchunk
610 if (!mmio
.Read(GetPointer(), GetSize())) {
611 dwLastError
= mmio
.GetLastError();
619 BOOL
PSound::Save(const PFilePath
& filename
)
621 PWaveFormat waveFormat
;
623 waveFormat
.SetFormat(numChannels
, sampleRate
, sampleSize
);
625 waveFormat
.SetSize(GetFormatInfoSize());
626 memcpy(waveFormat
.GetPointer(), GetFormatInfoData(), GetFormatInfoSize());
630 PMultiMediaFile mmio
;
631 if (!mmio
.CreateWaveFile(filename
, waveFormat
, GetSize())) {
632 dwLastError
= mmio
.GetLastError();
636 if (!mmio
.Write(GetPointer(), GetSize())) {
637 dwLastError
= mmio
.GetLastError();
647 PSoundChannel
channel(PSoundChannel::GetDefaultDevice(PSoundChannel::Player
),
648 PSoundChannel::Player
);
649 if (!channel
.IsOpen())
652 return channel
.PlaySound(*this, TRUE
);
655 BOOL
PSound::Play(const PString
& device
)
658 PSoundChannel
channel(device
,
659 PSoundChannel::Player
);
660 if (!channel
.IsOpen())
663 return channel
.PlaySound(*this, TRUE
);
666 BOOL
PSound::PlayFile(const PFilePath
& file
, BOOL wait
)
668 return ::PlaySound(file
, NULL
, SND_FILENAME
|(wait
? SND_SYNC
: SND_ASYNC
));
672 ///////////////////////////////////////////////////////////////////////////////
674 PWaveBuffer::PWaveBuffer(PINDEX sz
)
679 header
.dwFlags
= WHDR_DONE
;
683 PWaveBuffer::~PWaveBuffer()
689 PWaveBuffer
& PWaveBuffer::operator=(const PSound
& sound
)
691 PBYTEArray::operator=(sound
);
696 void PWaveBuffer::PrepareCommon(PINDEX count
)
700 memset(&header
, 0, sizeof(header
));
701 header
.lpData
= (char *)GetPointer();
702 header
.dwBufferLength
= count
;
703 header
.dwUser
= (DWORD
)this;
707 DWORD
PWaveBuffer::Prepare(HWAVEOUT hOut
, PINDEX
& count
)
709 // Set up WAVEHDR structure and prepare it to be written to wave device
710 if (count
> GetSize())
713 PrepareCommon(count
);
715 return waveOutPrepareHeader(hWaveOut
, &header
, sizeof(header
));
719 DWORD
PWaveBuffer::Prepare(HWAVEIN hIn
)
721 // Set up WAVEHDR structure and prepare it to be read from wave device
722 PrepareCommon(GetSize());
724 return waveInPrepareHeader(hWaveIn
, &header
, sizeof(header
));
728 DWORD
PWaveBuffer::Release()
730 DWORD err
= MMSYSERR_NOERROR
;
732 // There seems to be some pathalogical cases where on an Abort() call the buffers
733 // still are "in use", even though waveOutReset() was called. So wait until the
734 // sound driver has finished with the buffer before releasing it.
736 if (hWaveOut
!= NULL
) {
737 if ((err
= waveOutUnprepareHeader(hWaveOut
, &header
, sizeof(header
))) == WAVERR_STILLPLAYING
)
742 if (hWaveIn
!= NULL
) {
743 if ((err
= waveInUnprepareHeader(hWaveIn
, &header
, sizeof(header
))) == WAVERR_STILLPLAYING
)
748 header
.dwFlags
|= WHDR_DONE
;
753 ///////////////////////////////////////////////////////////////////////////////
759 PSoundChannelWin32::PSoundChannelWin32()
765 PSoundChannelWin32::PSoundChannelWin32(const PString
& device
,
767 unsigned numChannels
,
769 unsigned bitsPerSample
)
772 Open(device
, dir
, numChannels
, sampleRate
, bitsPerSample
);
776 void PSoundChannelWin32::Construct()
781 hEventDone
= CreateEvent(NULL
, FALSE
, FALSE
, NULL
);
783 waveFormat
.SetFormat(1, 8000, 16);
785 bufferByteOffset
= P_MAX_INDEX
;
787 SetBuffers(32768, 3);
791 PSoundChannelWin32::~PSoundChannelWin32()
795 if (hEventDone
!= NULL
)
796 CloseHandle(hEventDone
);
800 PString
PSoundChannelWin32::GetName() const
806 PStringArray
PSoundChannelWin32::GetDeviceNames(Directions dir
)
810 unsigned numDevs
, id
;
814 numDevs
= waveOutGetNumDevs();
815 for (id
= 0; id
< numDevs
; id
++) {
817 if (waveOutGetDevCaps(id
, &caps
, sizeof(caps
)) == 0)
818 array
[array
.GetSize()] = caps
.szPname
;
823 numDevs
= waveInGetNumDevs();
824 for (id
= 0; id
< numDevs
; id
++) {
826 if (waveInGetDevCaps(id
, &caps
, sizeof(caps
)) == 0)
827 array
[array
.GetSize()] = caps
.szPname
;
836 PString
PSoundChannelWin32::GetDefaultDevice(Directions dir
)
838 RegistryKey
registry("HKEY_CURRENT_USER\\Software\\Microsoft\\Multimedia\\Sound Mapper",
839 RegistryKey::ReadOnly
);
844 if (!registry
.QueryValue("Playback", str
)) {
846 if (waveOutGetDevCaps(0, &caps
, sizeof(caps
)) == 0)
851 if (!registry
.QueryValue("Record", str
)) {
853 if (waveInGetDevCaps(0, &caps
, sizeof(caps
)) == 0)
861 BOOL
PSoundChannelWin32::GetDeviceID(const PString
& device
, Directions dir
, unsigned& id
)
865 if (device
[0] == '#') {
866 id
= device
.Mid(1).AsUnsigned();
869 if (id
< waveOutGetNumDevs()) {
871 if (waveOutGetDevCaps(id
, &caps
, sizeof(caps
)) == 0) {
872 deviceName
= caps
.szPname
;
879 if (id
< waveInGetNumDevs()) {
881 if (waveInGetDevCaps(id
, &caps
, sizeof(caps
)) == 0) {
882 deviceName
= caps
.szPname
;
892 for (id
= 0; id
< waveOutGetNumDevs(); id
++) {
894 if (waveOutGetDevCaps(id
, &caps
, sizeof(caps
)) == 0 &&
895 strcasecmp(caps
.szPname
, device
) == 0) {
896 deviceName
= caps
.szPname
;
904 for (id
= 0; id
< waveInGetNumDevs(); id
++) {
906 if (waveInGetDevCaps(id
, &caps
, sizeof(caps
)) == 0 &&
907 strcasecmp(caps
.szPname
, device
) == 0) {
908 deviceName
= caps
.szPname
;
918 return SetErrorValues(NotFound
, MMSYSERR_BADDEVICEID
|PWIN32ErrorFlag
);
923 BOOL
PSoundChannelWin32::Open(const PString
& device
,
925 unsigned numChannels
,
927 unsigned bitsPerSample
)
932 if( !GetDeviceID(device
, dir
, id
) )
935 waveFormat
.SetFormat(numChannels
, sampleRate
, bitsPerSample
);
938 return OpenDevice(id
);
941 BOOL
PSoundChannelWin32::Open(const PString
& device
,
943 const PWaveFormat
& format
)
948 if( !GetDeviceID(device
, dir
, id
) )
954 return OpenDevice(id
);
957 BOOL
PSoundChannelWin32::OpenDevice(unsigned id
)
961 PWaitAndSignal
mutex(bufferMutex
);
963 bufferByteOffset
= P_MAX_INDEX
;
966 WAVEFORMATEX
* format
= (WAVEFORMATEX
*) waveFormat
;
968 DWORD osError
= MMSYSERR_BADDEVICEID
;
971 osError
= waveOutOpen(&hWaveOut
, id
, format
,
972 (DWORD
)hEventDone
, 0, CALLBACK_EVENT
);
976 osError
= waveInOpen(&hWaveIn
, id
, format
,
977 (DWORD
)hEventDone
, 0, CALLBACK_EVENT
);
981 if (osError
!= MMSYSERR_NOERROR
)
982 return SetErrorValues(NotFound
, osError
|PWIN32ErrorFlag
);
988 BOOL
PSoundChannelWin32::IsOpen() const
990 return os_handle
>= 0;
993 BOOL
PSoundChannelWin32::SetFormat(unsigned numChannels
,
995 unsigned bitsPerSample
)
999 waveFormat
.SetFormat(numChannels
, sampleRate
, bitsPerSample
);
1001 return OpenDevice(os_handle
);
1005 BOOL
PSoundChannelWin32::SetFormat(const PWaveFormat
& format
)
1009 waveFormat
= format
;
1011 return OpenDevice(os_handle
);
1015 unsigned PSoundChannelWin32::GetChannels() const
1017 return waveFormat
->nChannels
;
1021 unsigned PSoundChannelWin32::GetSampleRate() const
1023 return waveFormat
->nSamplesPerSec
;
1027 unsigned PSoundChannelWin32::GetSampleSize() const
1029 return waveFormat
->wBitsPerSample
;
1033 BOOL
PSoundChannelWin32::Close()
1036 return SetErrorValues(NotOpen
, EBADF
);
1040 if (hWaveOut
!= NULL
) {
1041 while (waveOutClose(hWaveOut
) == WAVERR_STILLPLAYING
)
1042 waveOutReset(hWaveOut
);
1046 if (hWaveIn
!= NULL
) {
1047 while (waveInClose(hWaveIn
) == WAVERR_STILLPLAYING
)
1048 waveInReset(hWaveIn
);
1059 BOOL
PSoundChannelWin32::SetBuffers(PINDEX size
, PINDEX count
)
1063 PAssert(size
> 0 && count
> 0, PInvalidParameter
);
1067 PWaitAndSignal
mutex(bufferMutex
);
1069 if (!buffers
.SetSize(count
))
1072 for (PINDEX i
= 0; i
< count
; i
++) {
1073 if (buffers
.GetAt(i
) == NULL
)
1074 buffers
.SetAt(i
, new PWaveBuffer(size
));
1075 if (!buffers
[i
].SetSize(size
))
1080 bufferByteOffset
= P_MAX_INDEX
;
1087 BOOL
PSoundChannelWin32::GetBuffers(PINDEX
& size
, PINDEX
& count
)
1089 PWaitAndSignal
mutex(bufferMutex
);
1091 count
= buffers
.GetSize();
1096 size
= buffers
[0].GetSize();
1102 BOOL
PSoundChannelWin32::Write(const void * data
, PINDEX size
)
1106 if (hWaveOut
== NULL
)
1107 return SetErrorValues(NotOpen
, EBADF
, LastWriteError
);
1109 const BYTE
* ptr
= (const BYTE
*)data
;
1113 DWORD osError
= MMSYSERR_NOERROR
;
1115 PWaveBuffer
& buffer
= buffers
[bufferIndex
];
1116 while ((buffer
.header
.dwFlags
&WHDR_DONE
) == 0) {
1117 bufferMutex
.Signal();
1118 // No free buffers, so wait for one
1119 if (WaitForSingleObject(hEventDone
, INFINITE
) != WAIT_OBJECT_0
)
1120 return SetErrorValues(Miscellaneous
, ::GetLastError()|PWIN32ErrorFlag
, LastWriteError
);
1124 // Can't write more than a buffer full
1125 PINDEX count
= size
;
1126 if ((osError
= buffer
.Prepare(hWaveOut
, count
)) != MMSYSERR_NOERROR
)
1129 memcpy(buffer
.GetPointer(), ptr
, count
);
1131 if ((osError
= waveOutWrite(hWaveOut
, &buffer
.header
, sizeof(WAVEHDR
))) != MMSYSERR_NOERROR
)
1134 bufferIndex
= (bufferIndex
+1)%buffers
.GetSize();
1135 lastWriteCount
+= count
;
1140 bufferMutex
.Signal();
1143 return SetErrorValues(Miscellaneous
, osError
|PWIN32ErrorFlag
, LastWriteError
);
1149 BOOL
PSoundChannelWin32::PlaySound(const PSound
& sound
, BOOL wait
)
1157 GetBuffers(bufferSize
, bufferCount
);
1159 unsigned numChannels
= waveFormat
->nChannels
;
1160 unsigned sampleRate
= waveFormat
->nSamplesPerSec
;
1161 unsigned bitsPerSample
= waveFormat
->wBitsPerSample
;
1162 if (sound
.GetEncoding() == 0)
1163 ok
= SetFormat(sound
.GetChannels(), sound
.GetSampleRate(), sound
.GetSampleSize());
1165 waveFormat
.SetFormat(sound
.GetFormatInfoData(), sound
.GetFormatInfoSize());
1166 ok
= OpenDevice(os_handle
);
1172 // To avoid lots of copying of sound data, we fake the PSound buffer into
1173 // the internal buffers and play directly from the PSound object.
1175 PWaveBuffer
& buffer
= buffers
[0];
1179 PINDEX count
= sound
.GetSize();
1180 if ((osError
= buffer
.Prepare(hWaveOut
, count
)) == MMSYSERR_NOERROR
&&
1181 (osError
= waveOutWrite(hWaveOut
, &buffer
.header
, sizeof(WAVEHDR
))) == MMSYSERR_NOERROR
) {
1183 ok
= WaitForPlayCompletion();
1186 SetErrorValues(Miscellaneous
, osError
|PWIN32ErrorFlag
, LastWriteError
);
1190 bufferMutex
.Signal();
1193 SetFormat(numChannels
, sampleRate
, bitsPerSample
);
1194 SetBuffers(bufferSize
, bufferCount
);
1199 BOOL
PSoundChannelWin32::PlayFile(const PFilePath
& filename
, BOOL wait
)
1203 PMultiMediaFile mmio
;
1204 PWaveFormat fileFormat
;
1206 if (!mmio
.OpenWaveFile(filename
, fileFormat
, dataSize
))
1207 return SetErrorValues(NotOpen
, mmio
.GetLastError()|PWIN32ErrorFlag
, LastWriteError
);
1209 // Save old format and set to one loaded from file.
1210 unsigned numChannels
= waveFormat
->nChannels
;
1211 unsigned sampleRate
= waveFormat
->nSamplesPerSec
;
1212 unsigned bitsPerSample
= waveFormat
->wBitsPerSample
;
1213 waveFormat
= fileFormat
;
1214 if (!OpenDevice(os_handle
)) {
1215 SetFormat(numChannels
, sampleRate
, bitsPerSample
);
1221 DWORD osError
= MMSYSERR_NOERROR
;
1222 while (dataSize
> 0) {
1223 PWaveBuffer
& buffer
= buffers
[bufferIndex
];
1224 while ((buffer
.header
.dwFlags
&WHDR_DONE
) == 0) {
1225 bufferMutex
.Signal();
1226 // No free buffers, so wait for one
1227 if (WaitForSingleObject(hEventDone
, INFINITE
) != WAIT_OBJECT_0
) {
1228 osError
= ::GetLastError();
1229 SetFormat(numChannels
, sampleRate
, bitsPerSample
);
1230 return SetErrorValues(Miscellaneous
, osError
|PWIN32ErrorFlag
, LastWriteError
);
1235 // Can't write more than a buffer full
1236 PINDEX count
= dataSize
;
1237 if ((osError
= buffer
.Prepare(hWaveOut
, count
)) != MMSYSERR_NOERROR
)
1240 // Read the waveform data subchunk
1241 if (!mmio
.Read(buffer
.GetPointer(), count
)) {
1242 osError
= mmio
.GetLastError();
1246 if ((osError
= waveOutWrite(hWaveOut
, &buffer
.header
, sizeof(WAVEHDR
))) != MMSYSERR_NOERROR
)
1249 bufferIndex
= (bufferIndex
+1)%buffers
.GetSize();
1253 bufferMutex
.Signal();
1255 if (osError
!= MMSYSERR_NOERROR
) {
1256 SetFormat(numChannels
, sampleRate
, bitsPerSample
);
1257 return SetErrorValues(Miscellaneous
, osError
|PWIN32ErrorFlag
, LastWriteError
);
1260 if (dataSize
== 0 && wait
) {
1261 WaitForPlayCompletion();
1262 SetFormat(numChannels
, sampleRate
, bitsPerSample
);
1269 BOOL
PSoundChannelWin32::HasPlayCompleted()
1271 PWaitAndSignal
mutex(bufferMutex
);
1273 for (PINDEX i
= 0; i
< buffers
.GetSize(); i
++) {
1274 if ((buffers
[i
].header
.dwFlags
&WHDR_DONE
) == 0)
1282 BOOL
PSoundChannelWin32::WaitForPlayCompletion()
1284 while (!HasPlayCompleted()) {
1285 if (WaitForSingleObject(hEventDone
, INFINITE
) != WAIT_OBJECT_0
)
1293 BOOL
PSoundChannelWin32::StartRecording()
1295 PWaitAndSignal
mutex(bufferMutex
);
1297 // See if has started already.
1298 if (bufferByteOffset
!= P_MAX_INDEX
)
1303 // Start the first read, queue all the buffers
1304 for (PINDEX i
= 0; i
< buffers
.GetSize(); i
++) {
1305 PWaveBuffer
& buffer
= buffers
[i
];
1306 if ((osError
= buffer
.Prepare(hWaveIn
)) != MMSYSERR_NOERROR
)
1308 if ((osError
= waveInAddBuffer(hWaveIn
, &buffer
.header
, sizeof(WAVEHDR
))) != MMSYSERR_NOERROR
)
1312 bufferByteOffset
= 0;
1314 if ((osError
= waveInStart(hWaveIn
)) == MMSYSERR_NOERROR
) // start recording
1317 bufferByteOffset
= P_MAX_INDEX
;
1318 return SetErrorValues(Miscellaneous
, osError
|PWIN32ErrorFlag
, LastReadError
);
1322 BOOL
PSoundChannelWin32::Read(void * data
, PINDEX size
)
1326 if (hWaveIn
== NULL
)
1327 return SetErrorValues(NotOpen
, EBADF
, LastReadError
);
1329 if (!WaitForRecordBufferFull())
1332 PWaitAndSignal
mutex(bufferMutex
);
1334 // Check to see if Abort() was called in another thread
1335 if (bufferByteOffset
== P_MAX_INDEX
)
1338 PWaveBuffer
& buffer
= buffers
[bufferIndex
];
1340 lastReadCount
= buffer
.header
.dwBytesRecorded
- bufferByteOffset
;
1341 if (lastReadCount
> size
)
1342 lastReadCount
= size
;
1344 memcpy(data
, &buffer
[bufferByteOffset
], lastReadCount
);
1346 bufferByteOffset
+= lastReadCount
;
1347 if (bufferByteOffset
>= (PINDEX
)buffer
.header
.dwBytesRecorded
) {
1349 if ((osError
= buffer
.Prepare(hWaveIn
)) != MMSYSERR_NOERROR
)
1350 return SetErrorValues(Miscellaneous
, osError
|PWIN32ErrorFlag
, LastReadError
);
1351 if ((osError
= waveInAddBuffer(hWaveIn
, &buffer
.header
, sizeof(WAVEHDR
))) != MMSYSERR_NOERROR
)
1352 return SetErrorValues(Miscellaneous
, osError
|PWIN32ErrorFlag
, LastReadError
);
1354 bufferIndex
= (bufferIndex
+1)%buffers
.GetSize();
1355 bufferByteOffset
= 0;
1362 BOOL
PSoundChannelWin32::RecordSound(PSound
& sound
)
1364 if (!WaitForAllRecordBuffersFull())
1367 sound
.SetFormat(waveFormat
->nChannels
,
1368 waveFormat
->nSamplesPerSec
,
1369 waveFormat
->wBitsPerSample
);
1371 PWaitAndSignal
mutex(bufferMutex
);
1373 if (buffers
.GetSize() == 1 &&
1374 (PINDEX
)buffers
[0].header
.dwBytesRecorded
== buffers
[0].GetSize())
1377 PINDEX totalSize
= 0;
1379 for (i
= 0; i
< buffers
.GetSize(); i
++)
1380 totalSize
+= buffers
[i
].header
.dwBytesRecorded
;
1382 if (!sound
.SetSize(totalSize
))
1383 return SetErrorValues(NoMemory
, ENOMEM
, LastReadError
);
1385 BYTE
* ptr
= sound
.GetPointer();
1386 for (i
= 0; i
< buffers
.GetSize(); i
++) {
1387 PINDEX sz
= buffers
[i
].header
.dwBytesRecorded
;
1388 memcpy(ptr
, buffers
[i
], sz
);
1397 BOOL
PSoundChannelWin32::RecordFile(const PFilePath
& filename
)
1399 if (!WaitForAllRecordBuffersFull())
1402 PWaitAndSignal
mutex(bufferMutex
);
1404 PINDEX dataSize
= 0;
1406 for (i
= 0; i
< buffers
.GetSize(); i
++)
1407 dataSize
+= buffers
[i
].header
.dwBytesRecorded
;
1409 PMultiMediaFile mmio
;
1410 if (!mmio
.CreateWaveFile(filename
, waveFormat
, dataSize
))
1411 return SetErrorValues(Miscellaneous
, mmio
.GetLastError()|PWIN32ErrorFlag
, LastReadError
);
1413 for (i
= 0; i
< buffers
.GetSize(); i
++) {
1414 if (!mmio
.Write(buffers
[i
], buffers
[i
].header
.dwBytesRecorded
))
1415 return SetErrorValues(Miscellaneous
, mmio
.GetLastError()|PWIN32ErrorFlag
, LastReadError
);
1422 BOOL
PSoundChannelWin32::IsRecordBufferFull()
1424 PWaitAndSignal
mutex(bufferMutex
);
1426 return (buffers
[bufferIndex
].header
.dwFlags
&WHDR_DONE
) != 0 &&
1427 buffers
[bufferIndex
].header
.dwBytesRecorded
> 0;
1431 BOOL
PSoundChannelWin32::AreAllRecordBuffersFull()
1433 PWaitAndSignal
mutex(bufferMutex
);
1435 for (PINDEX i
= 0; i
< buffers
.GetSize(); i
++) {
1436 if ((buffers
[i
].header
.dwFlags
&WHDR_DONE
) == 0 ||
1437 buffers
[i
].header
.dwBytesRecorded
== 0)
1445 BOOL
PSoundChannelWin32::WaitForRecordBufferFull()
1447 if (!StartRecording()) // Start the first read, queue all the buffers
1450 while (!IsRecordBufferFull()) {
1451 if (WaitForSingleObject(hEventDone
, INFINITE
) != WAIT_OBJECT_0
)
1454 PWaitAndSignal
mutex(bufferMutex
);
1455 if (bufferByteOffset
== P_MAX_INDEX
)
1463 BOOL
PSoundChannelWin32::WaitForAllRecordBuffersFull()
1465 if (!StartRecording()) // Start the first read, queue all the buffers
1468 while (!AreAllRecordBuffersFull()) {
1469 if (WaitForSingleObject(hEventDone
, INFINITE
) != WAIT_OBJECT_0
)
1472 PWaitAndSignal
mutex(bufferMutex
);
1473 if (bufferByteOffset
== P_MAX_INDEX
)
1481 BOOL
PSoundChannelWin32::Abort()
1483 DWORD osError
= MMSYSERR_NOERROR
;
1485 if (hWaveOut
!= NULL
)
1486 osError
= waveOutReset(hWaveOut
);
1488 if (hWaveIn
!= NULL
)
1489 osError
= waveInReset(hWaveIn
);
1492 PWaitAndSignal
mutex(bufferMutex
);
1494 if (hWaveOut
!= NULL
|| hWaveIn
!= NULL
) {
1495 for (PINDEX i
= 0; i
< buffers
.GetSize(); i
++) {
1496 while (buffers
[i
].Release() == WAVERR_STILLPLAYING
) {
1497 if (hWaveOut
!= NULL
)
1498 waveOutReset(hWaveOut
);
1499 if (hWaveIn
!= NULL
)
1500 waveInReset(hWaveIn
);
1505 bufferByteOffset
= P_MAX_INDEX
;
1508 // Signal any threads waiting on this event, they should then check
1509 // the bufferByteOffset variable for an abort.
1510 SetEvent(hEventDone
);
1513 if (osError
!= MMSYSERR_NOERROR
)
1514 return SetErrorValues(Miscellaneous
, osError
|PWIN32ErrorFlag
);
1520 PString
PSoundChannelWin32::GetErrorText(ErrorGroup group
) const
1524 if ((lastErrorNumber
[group
]&PWIN32ErrorFlag
) == 0)
1525 return PChannel::GetErrorText(group
);
1527 DWORD osError
= lastErrorNumber
[group
]&~PWIN32ErrorFlag
;
1528 if (direction
== Recorder
) {
1529 if (waveInGetErrorText(osError
, str
.GetPointer(256), 256) != MMSYSERR_NOERROR
)
1530 return PChannel::GetErrorText(group
);
1533 if (waveOutGetErrorText(osError
, str
.GetPointer(256), 256) != MMSYSERR_NOERROR
)
1534 return PChannel::GetErrorText(group
);
1541 BOOL
PSoundChannelWin32::SetVolume(unsigned newVolume
)
1544 return SetErrorValues(NotOpen
, EBADF
);
1546 DWORD rawVolume
= newVolume
*65536/100;
1547 if (rawVolume
> 65535)
1551 if (waveOutGetDevCaps((UINT
) hWaveOut
, &caps
, sizeof(caps
)) == MMSYSERR_NOERROR
) {
1552 // If the device does not support L/R volume only the low word matters
1553 if ((caps
.dwSupport
& WAVECAPS_LRVOLUME
) != 0) {
1555 DWORD oldVolume
= 0;
1556 if (waveOutGetVolume(hWaveOut
, &oldVolume
) == MMSYSERR_NOERROR
) {
1557 // GetVolume() is supposed to return the value we intended to set.
1558 // So do the proper calculations
1559 // 1. (L + R) / 2 = rawVolume -> GetVolume() formula
1560 // 2. L / R = oldL / oldR -> Unmodified balance
1562 // oldL = LOWORD(oldVolume)
1563 // oldR = HIWORD(oldVolume)
1568 // Old volume values
1569 oldL
= LOWORD(oldVolume
);
1570 oldR
= HIWORD(oldVolume
);
1574 // First sort out extreme cases
1576 rVol
= lVol
= rawVolume
;
1577 else if ( oldL
== 0 )
1579 else if ( oldR
== 0 )
1583 rVol
= ::MulDiv( 2 * rawVolume
, oldR
, oldL
+ oldR
);
1584 lVol
= ::MulDiv( rVol
, oldL
, oldR
);
1586 rVol
= 2 * rawVolume
* oldR
/ ( oldL
+ oldR
);
1587 lVol
= rVol
* oldL
/ oldR
;
1591 rawVolume
= MAKELPARAM(lVol
, rVol
);
1594 // Couldn't get current volume. Assume centered balance
1595 rawVolume
= MAKELPARAM(rawVolume
, rawVolume
);
1600 // Couldn't get device caps. Assume centered balance
1601 // If the device does not support independant L/R volume
1602 // the high-order word (R) is ignored
1603 rawVolume
= MAKELPARAM(rawVolume
, rawVolume
);
1606 if (direction
== Recorder
) {
1607 // Does not appear to be an input volume!!
1610 DWORD osError
= waveOutSetVolume(hWaveOut
, rawVolume
);
1611 if (osError
!= MMSYSERR_NOERROR
)
1612 return SetErrorValues(Miscellaneous
, osError
|PWIN32ErrorFlag
);
1620 BOOL
PSoundChannelWin32::GetVolume(unsigned & oldVolume
)
1623 return SetErrorValues(NotOpen
, EBADF
);
1625 DWORD rawVolume
= 0;
1627 if (direction
== Recorder
) {
1628 // Does not appear to be an input volume!!
1631 DWORD osError
= waveOutGetVolume(hWaveOut
, &rawVolume
);
1632 if (osError
!= MMSYSERR_NOERROR
)
1633 return SetErrorValues(Miscellaneous
, osError
|PWIN32ErrorFlag
);
1637 if (waveOutGetDevCaps((UINT
) hWaveOut
, &caps
, sizeof(caps
)) == MMSYSERR_NOERROR
&&
1638 (caps
.dwSupport
& WAVECAPS_LRVOLUME
) != 0)
1639 rawVolume
= (HIWORD(rawVolume
) + LOWORD(rawVolume
)) / 2;
1641 oldVolume
= rawVolume
*100/65536;
1645 // End of File ///////////////////////////////////////////////////////////////