4 * Sound driver implementation.
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): Loopback feature: Philip Edelbrock <phil@netroedge.com>.
30 * Revision 1.3 2002/02/09 00:52:01 robertj
31 * Slight adjustment to API and documentation for volume functions.
33 * Revision 1.2 2002/02/07 20:57:21 dereks
34 * add SetVolume and GetVolume methods to PSoundChannel
36 * Revision 1.1 2000/06/21 01:01:22 robertj
37 * AIX port, thanks Wolfgang Platzer (wolfgang.platzer@infonova.at).
39 * Revision 1.17 2000/05/11 02:05:54 craigs
40 * Fixed problem with PLayFile not recognizing wait flag
42 * Revision 1.16 2000/05/10 02:10:44 craigs
43 * Added implementation for PlayFile command
45 * Revision 1.15 2000/05/02 08:30:26 craigs
46 * Removed "memory leaks" caused by brain-dead GNU linker
48 * Revision 1.14 2000/04/09 18:19:23 rogerh
49 * Add my changes for NetBSD support.
51 * Revision 1.13 2000/03/08 12:17:09 rogerh
54 * Revision 1.12 2000/03/04 13:02:28 robertj
55 * Added simple play functions for sound files.
57 * Revision 1.11 2000/02/15 23:11:34 robertj
58 * Audio support for FreeBSD, thanks Roger Hardiman.
60 * Revision 1.10 2000/01/08 06:41:08 craigs
61 * Fixed problem whereby failure to open sound device returns TRUE
63 * Revision 1.9 1999/08/24 13:40:26 craigs
64 * Fixed problem with EINTR causing sound channel reads and write to fail
65 * Thanks to phil@netroedge.com!
67 * Revision 1.8 1999/08/17 09:42:22 robertj
68 * Fixed close of sound channel in loopback mode closing stdin!
70 * Revision 1.7 1999/08/17 09:28:47 robertj
71 * Added audio loopback psuedo-device (thanks Philip Edelbrock)
73 * Revision 1.6 1999/07/19 01:31:49 craigs
74 * Major rewrite to assure ioctls are all done in the correct order as OSS seems
75 * to be incredibly sensitive to this.
77 * Revision 1.5 1999/07/11 13:42:13 craigs
78 * pthreads support for Linux
80 * Revision 1.4 1999/06/30 13:49:26 craigs
81 * Added code to allow full duplex audio
83 * Revision 1.3 1999/05/28 14:14:29 robertj
84 * Added function to get default audio device.
86 * Revision 1.2 1999/05/22 12:49:05 craigs
87 * Finished implementation for Linux OSS interface
89 * Revision 1.1 1999/02/25 03:45:00 robertj
90 * Sound driver implementation changes for various unix platforms.
92 * Revision 1.1 1999/02/22 13:24:47 robertj
93 * Added first cut sound implmentation.
97 #pragma implementation "sound.h"
102 #include <sys/soundcard.h>
103 #include <sys/time.h>
107 #include <machine/soundcard.h>
110 #if defined(P_OPENBSD) || defined(P_NETBSD)
111 #include <soundcard.h>
115 ///////////////////////////////////////////////////////////////////////////////
116 // declare type for sound handle dictionary
118 class SoundHandleEntry
: public PObject
{
120 PCLASSINFO(SoundHandleEntry
, PObject
)
128 unsigned numChannels
;
130 unsigned bitsPerSample
;
131 unsigned fragmentValue
;
135 PDICTIONARY(SoundHandleDict
, PString
, SoundHandleEntry
);
137 #define LOOPBACK_BUFFER_SIZE 5000
138 #define BYTESINBUF ((startptr<endptr)?(endptr-startptr):(LOOPBACK_BUFFER_SIZE+endptr-startptr))
140 static char buffer
[LOOPBACK_BUFFER_SIZE
];
141 static int startptr
, endptr
;
143 PMutex
PSoundChannel::dictMutex
;
145 static SoundHandleDict
& handleDict()
147 static SoundHandleDict dict
;
151 PSound::PSound(unsigned channels
,
152 unsigned samplesPerSecond
,
153 unsigned bitsPerSample
,
158 numChannels
= channels
;
159 sampleRate
= samplesPerSecond
;
160 sampleSize
= bitsPerSample
;
163 memcpy(GetPointer(), buffer
, bufferSize
);
167 PSound::PSound(const PFilePath
& filename
)
177 PSound
& PSound::operator=(const PBYTEArray
& data
)
179 PBYTEArray::operator=(data
);
184 void PSound::SetFormat(unsigned channels
,
185 unsigned samplesPerSecond
,
186 unsigned bitsPerSample
)
189 numChannels
= channels
;
190 sampleRate
= samplesPerSecond
;
191 sampleSize
= bitsPerSample
;
192 formatInfo
.SetSize(0);
196 BOOL
PSound::Load(const PFilePath
& /*filename*/)
202 BOOL
PSound::Save(const PFilePath
& /*filename*/)
210 PSoundChannel
channel(PSoundChannel::GetDefaultDevice(PSoundChannel::Player
),
211 PSoundChannel::Player
);
212 if (!channel
.IsOpen())
215 return channel
.PlaySound(*this, TRUE
);
219 BOOL
PSound::PlayFile(const PFilePath
& file
, BOOL wait
)
221 PSoundChannel
channel(PSoundChannel::GetDefaultDevice(PSoundChannel::Player
),
222 PSoundChannel::Player
);
223 if (!channel
.IsOpen())
226 return channel
.PlayFile(file
, wait
);
230 ///////////////////////////////////////////////////////////////////////////////
232 SoundHandleEntry::SoundHandleEntry()
238 ///////////////////////////////////////////////////////////////////////////////
240 PSoundChannel::PSoundChannel()
246 PSoundChannel::PSoundChannel(const PString
& device
,
248 unsigned numChannels
,
250 unsigned bitsPerSample
)
253 Open(device
, dir
, numChannels
, sampleRate
, bitsPerSample
);
257 void PSoundChannel::Construct()
263 PSoundChannel::~PSoundChannel()
269 PStringArray
PSoundChannel::GetDeviceNames(Directions
/*dir*/)
271 static const char * const devices
[] = {
278 return PStringArray(PARRAYSIZE(devices
), devices
);
282 PString
PSoundChannel::GetDefaultDevice(Directions
/*dir*/)
288 BOOL
PSoundChannel::Open(const PString
& _device
,
290 unsigned _numChannels
,
291 unsigned _sampleRate
,
292 unsigned _bitsPerSample
)
296 // lock the dictionary
299 // make the direction value 1 or 2
302 // if this device in in the dictionary
303 if (handleDict().Contains(_device
)) {
305 SoundHandleEntry
& entry
= handleDict()[_device
];
307 // see if the sound channel is already open in this direction
308 if ((entry
.direction
& dir
) != 0) {
313 // flag this entry as open in this direction
314 entry
.direction
|= dir
;
315 os_handle
= entry
.handle
;
319 // this is the first time this device has been used
320 // open the device in read/write mode always
321 if (_device
== "loopback") {
322 startptr
= endptr
= 0;
323 os_handle
= 0; // Use os_handle value 0 to indicate loopback, cannot ever be stdin!
325 else if (!ConvertOSError(os_handle
= ::open((const char *)_device
, O_RDWR
))) {
330 // add the device to the dictionary
331 SoundHandleEntry
* entry
= PNEW SoundHandleEntry
;
332 handleDict().SetAt(_device
, entry
);
334 // save the information into the dictionary entry
335 entry
->handle
= os_handle
;
336 entry
->direction
= dir
;
337 entry
->numChannels
= _numChannels
;
338 entry
->sampleRate
= _sampleRate
;
339 entry
->bitsPerSample
= _bitsPerSample
;
340 entry
->isInitialised
= FALSE
;
341 entry
->fragmentValue
= 0x7fff0008;
345 // unlock the dictionary
348 // save the direction and device
351 isInitialised
= FALSE
;
356 BOOL
PSoundChannel::Setup()
364 // lock the dictionary
367 // the device must always be in the dictionary
368 PAssertOS(handleDict().Contains(device
));
370 // get record for the device
371 SoundHandleEntry
& entry
= handleDict()[device
];
374 if (entry
.isInitialised
) {
375 isInitialised
= TRUE
;
377 } else if (device
== "loopback")
381 // must always set paramaters in the following order:
383 // sample format (number of bits)
384 // number of channels (mon/stereo)
385 // speed (sampling rate)
389 // reset the device first so it will accept the new parms
392 if (ConvertOSError(::ioctl(os_handle
, SNDCTL_DSP_RESET
, &arg
))) {
394 arg
= val
= entry
.fragmentValue
;
395 //if (ConvertOSError(ioctl(os_handle, SNDCTL_DSP_SETFRAGMENT, &arg)) || (arg != val)) {
396 ::ioctl(os_handle
, SNDCTL_DSP_SETFRAGMENT
, &arg
); {
398 arg
= val
= (entry
.bitsPerSample
== 16) ? AFMT_S16_LE
: AFMT_S8
;
399 if (ConvertOSError(::ioctl(os_handle
, SNDCTL_DSP_SETFMT
, &arg
)) || (arg
!= val
)) {
401 arg
= val
= (entry
.numChannels
== 2) ? 1 : 0;
402 if (ConvertOSError(::ioctl(os_handle
, SNDCTL_DSP_STEREO
, &arg
)) || (arg
!= val
)) {
404 arg
= val
= entry
.sampleRate
;
405 if (ConvertOSError(::ioctl(os_handle
, SNDCTL_DSP_SPEED
, &arg
)) || (arg
!= val
))
415 entry
.isInitialised
= TRUE
;
416 isInitialised
= TRUE
;
423 BOOL
PSoundChannel::Close()
425 // if the channel isn't open, do nothing
429 if (os_handle
== 0) {
434 // the device must be in the dictionary
436 SoundHandleEntry
* entry
;
437 PAssert((entry
= handleDict().GetAt(device
)) != NULL
, "Unknown sound device \"" + device
+ "\" found");
439 // modify the directions bit mask in the dictionary
440 entry
->direction
^= (direction
+1);
442 // if this is the last usage of this entry, then remove it
443 if (entry
->direction
== 0) {
444 handleDict().RemoveAt(device
);
446 return PChannel::Close();
449 // flag this channel as closed
455 BOOL
PSoundChannel::Write(const void * buf
, PINDEX len
)
461 while (!ConvertOSError(::write(os_handle
, (void *)buf
, len
)))
462 if (GetErrorCode() != Interrupted
)
471 buffer
[endptr
++] = ((char *)buf
)[index
++];
472 if (endptr
== LOOPBACK_BUFFER_SIZE
)
474 while (((startptr
- 1) == endptr
) || ((endptr
==LOOPBACK_BUFFER_SIZE
- 1) && (startptr
==0))) {
481 BOOL
PSoundChannel::Read(void * buf
, PINDEX len
)
487 while (!ConvertOSError(::read(os_handle
, (void *)buf
, len
)))
488 if (GetErrorCode() != Interrupted
)
496 while (startptr
== endptr
)
499 ((char *)buf
)[index
++]=buffer
[startptr
++];
500 if (startptr
== LOOPBACK_BUFFER_SIZE
)
507 BOOL
PSoundChannel::SetFormat(unsigned numChannels
,
509 unsigned bitsPerSample
)
517 PAssert((bitsPerSample
== 8) || (bitsPerSample
== 16), PInvalidParameter
);
518 PAssert(numChannels
>= 1 && numChannels
<= 2, PInvalidParameter
);
522 // lock the dictionary
525 // the device must always be in the dictionary
526 PAssertOS(handleDict().Contains(device
));
528 // get record for the device
529 SoundHandleEntry
& entry
= handleDict()[device
];
531 entry
.numChannels
= numChannels
;
532 entry
.sampleRate
= sampleRate
;
533 entry
.bitsPerSample
= bitsPerSample
;
534 entry
.isInitialised
= FALSE
;
539 // mark this channel as uninitialised
540 isInitialised
= FALSE
;
546 BOOL
PSoundChannel::SetBuffers(PINDEX size
, PINDEX count
)
555 PAssert(size
> 0 && count
> 0 && count
< 65536, PInvalidParameter
);
557 while (size
> (PINDEX
)(1 << arg
))
562 // lock the dictionary
565 // the device must always be in the dictionary
566 PAssertOS(handleDict().Contains(device
));
568 // get record for the device
569 SoundHandleEntry
& entry
= handleDict()[device
];
571 // set information in the common record
572 entry
.fragmentValue
= arg
;
573 entry
.isInitialised
= FALSE
;
575 // flag this channel as not initialised
576 isInitialised
= FALSE
;
584 BOOL
PSoundChannel::GetBuffers(PINDEX
& size
, PINDEX
& count
)
591 // lock the dictionary
594 // the device must always be in the dictionary
595 PAssertOS(handleDict().Contains(device
));
597 SoundHandleEntry
& entry
= handleDict()[device
];
599 int arg
= entry
.fragmentValue
;
604 size
= 1 << (arg
&0xffff);
609 BOOL
PSoundChannel::PlaySound(const PSound
& sound
, BOOL wait
)
618 if (!Write((const BYTE
*)sound
, sound
.GetSize()))
622 return WaitForPlayCompletion();
628 BOOL
PSoundChannel::PlayFile(const PFilePath
& filename
, BOOL wait
)
635 PFile
file(filename
, PFile::ReadOnly
);
641 if (!file
.Read(buffer
, 256))
643 PINDEX len
= file
.GetLastReadCount();
646 if (!Write(buffer
, len
))
653 return WaitForPlayCompletion();
659 BOOL
PSoundChannel::HasPlayCompleted()
667 return BYTESINBUF
<= 0;
671 if (!ConvertOSError(::ioctl(os_handle
, SNDCTL_DSP_GETOSPACE
, &info
)))
674 return info
.fragments
== info
.fragstotal
;
682 BOOL
PSoundChannel::WaitForPlayCompletion()
689 if (os_handle
== 0) {
690 while (BYTESINBUF
> 0)
695 return ConvertOSError(::ioctl(os_handle
, SNDCTL_DSP_SYNC
, NULL
));
702 BOOL
PSoundChannel::RecordSound(PSound
& sound
)
713 BOOL
PSoundChannel::RecordFile(const PFilePath
& filename
)
724 BOOL
PSoundChannel::StartRecording()
736 FD_SET(os_handle
, &fds
);
738 struct timeval timeout
;
739 memset(&timeout
, 0, sizeof(timeout
));
741 return ConvertOSError(::select(1, &fds
, NULL
, NULL
, &timeout
));
745 BOOL
PSoundChannel::IsRecordBufferFull()
753 return (BYTESINBUF
> 0);
757 if (!ConvertOSError(::ioctl(os_handle
, SNDCTL_DSP_GETISPACE
, &info
)))
760 return info
.fragments
> 0;
767 BOOL
PSoundChannel::AreAllRecordBuffersFull()
775 return (BYTESINBUF
== LOOPBACK_BUFFER_SIZE
);
779 if (!ConvertOSError(::ioctl(os_handle
, SNDCTL_DSP_GETISPACE
, &info
)))
782 return info
.fragments
== info
.fragstotal
;
789 BOOL
PSoundChannel::WaitForRecordBufferFull()
796 return PXSetIOBlock(PXReadBlock
, readTimeout
);
800 BOOL
PSoundChannel::WaitForAllRecordBuffersFull()
806 BOOL
PSoundChannel::Abort()
808 if (os_handle
== 0) {
809 startptr
= endptr
= 0;
814 return ConvertOSError(ioctl(os_handle
, SNDCTL_DSP_RESET
, NULL
));
820 BOOL
PSoundChannel::SetVolume(unsigned newVolume
)
822 cerr
<< __FILE__
<< "PSoundChannel :: SetVolume called in error. Please fix"<<endl
;
826 BOOL
PSoundChannel::GetVolume(unsigned & volume
)
828 cerr
<< __FILE__
<< "PSoundChannel :: GetVolume called in error. Please fix"<<endl
;