Uncommented beaudio code
[pwlib.git] / src / ptlib / unix / ossaix.cxx
blobbd8bf3870053241a01863362f5fea7d35e085c89
1 /*
2 * sound.cxx
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
18 * under the License.
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>.
29 * $Log$
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
52 * Add OpenBSD support
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"
99 #include <ptlib.h>
101 #ifdef P_LINUX
102 #include <sys/soundcard.h>
103 #include <sys/time.h>
104 #endif
106 #ifdef P_FREEBSD
107 #include <machine/soundcard.h>
108 #endif
110 #if defined(P_OPENBSD) || defined(P_NETBSD)
111 #include <soundcard.h>
112 #endif
115 ///////////////////////////////////////////////////////////////////////////////
116 // declare type for sound handle dictionary
118 class SoundHandleEntry : public PObject {
120 PCLASSINFO(SoundHandleEntry, PObject)
122 public:
123 SoundHandleEntry();
125 int handle;
126 int direction;
128 unsigned numChannels;
129 unsigned sampleRate;
130 unsigned bitsPerSample;
131 unsigned fragmentValue;
132 BOOL isInitialised;
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;
148 return dict;
151 PSound::PSound(unsigned channels,
152 unsigned samplesPerSecond,
153 unsigned bitsPerSample,
154 PINDEX bufferSize,
155 const BYTE * buffer)
157 encoding = 0;
158 numChannels = channels;
159 sampleRate = samplesPerSecond;
160 sampleSize = bitsPerSample;
161 SetSize(bufferSize);
162 if (buffer != NULL)
163 memcpy(GetPointer(), buffer, bufferSize);
167 PSound::PSound(const PFilePath & filename)
169 encoding = 0;
170 numChannels = 1;
171 sampleRate = 8000;
172 sampleSize = 16;
173 Load(filename);
177 PSound & PSound::operator=(const PBYTEArray & data)
179 PBYTEArray::operator=(data);
180 return *this;
184 void PSound::SetFormat(unsigned channels,
185 unsigned samplesPerSecond,
186 unsigned bitsPerSample)
188 encoding = 0;
189 numChannels = channels;
190 sampleRate = samplesPerSecond;
191 sampleSize = bitsPerSample;
192 formatInfo.SetSize(0);
196 BOOL PSound::Load(const PFilePath & /*filename*/)
198 return FALSE;
202 BOOL PSound::Save(const PFilePath & /*filename*/)
204 return FALSE;
208 BOOL PSound::Play()
210 PSoundChannel channel(PSoundChannel::GetDefaultDevice(PSoundChannel::Player),
211 PSoundChannel::Player);
212 if (!channel.IsOpen())
213 return FALSE;
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())
224 return FALSE;
226 return channel.PlayFile(file, wait);
230 ///////////////////////////////////////////////////////////////////////////////
232 SoundHandleEntry::SoundHandleEntry()
234 handle = -1;
235 direction = 0;
238 ///////////////////////////////////////////////////////////////////////////////
240 PSoundChannel::PSoundChannel()
242 Construct();
246 PSoundChannel::PSoundChannel(const PString & device,
247 Directions dir,
248 unsigned numChannels,
249 unsigned sampleRate,
250 unsigned bitsPerSample)
252 Construct();
253 Open(device, dir, numChannels, sampleRate, bitsPerSample);
257 void PSoundChannel::Construct()
259 os_handle = -1;
263 PSoundChannel::~PSoundChannel()
265 Close();
269 PStringArray PSoundChannel::GetDeviceNames(Directions /*dir*/)
271 static const char * const devices[] = {
272 "/dev/audio",
273 "/dev/dsp",
274 "/dev/dspW",
275 "loopback"
278 return PStringArray(PARRAYSIZE(devices), devices);
282 PString PSoundChannel::GetDefaultDevice(Directions /*dir*/)
284 return "/dev/dsp";
288 BOOL PSoundChannel::Open(const PString & _device,
289 Directions _dir,
290 unsigned _numChannels,
291 unsigned _sampleRate,
292 unsigned _bitsPerSample)
294 Close();
296 // lock the dictionary
297 dictMutex.Wait();
299 // make the direction value 1 or 2
300 int dir = _dir + 1;
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) {
309 dictMutex.Signal();
310 return FALSE;
313 // flag this entry as open in this direction
314 entry.direction |= dir;
315 os_handle = entry.handle;
317 } else {
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))) {
326 dictMutex.Signal();
327 return FALSE;
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
346 dictMutex.Signal();
348 // save the direction and device
349 direction = _dir;
350 device = _device;
351 isInitialised = FALSE;
353 return TRUE;
356 BOOL PSoundChannel::Setup()
358 if (os_handle < 0)
359 return FALSE;
361 if (isInitialised)
362 return TRUE;
364 // lock the dictionary
365 dictMutex.Wait();
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];
373 BOOL stat = FALSE;
374 if (entry.isInitialised) {
375 isInitialised = TRUE;
376 stat = TRUE;
377 } else if (device == "loopback")
378 stat = TRUE;
379 else {
381 // must always set paramaters in the following order:
382 // buffer paramaters
383 // sample format (number of bits)
384 // number of channels (mon/stereo)
385 // speed (sampling rate)
387 int arg, val;
389 // reset the device first so it will accept the new parms
391 #ifndef P_AIX
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))
406 stat = TRUE;
411 #endif
415 entry.isInitialised = TRUE;
416 isInitialised = TRUE;
418 dictMutex.Signal();
420 return stat;
423 BOOL PSoundChannel::Close()
425 // if the channel isn't open, do nothing
426 if (os_handle < 0)
427 return TRUE;
429 if (os_handle == 0) {
430 os_handle = -1;
431 return TRUE;
434 // the device must be in the dictionary
435 dictMutex.Wait();
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);
445 dictMutex.Signal();
446 return PChannel::Close();
449 // flag this channel as closed
450 dictMutex.Signal();
451 os_handle = -1;
452 return TRUE;
455 BOOL PSoundChannel::Write(const void * buf, PINDEX len)
457 if (!Setup())
458 return FALSE;
460 if (os_handle > 0) {
461 while (!ConvertOSError(::write(os_handle, (void *)buf, len)))
462 if (GetErrorCode() != Interrupted)
463 return FALSE;
464 return TRUE;
467 int index = 0;
469 while (len > 0) {
470 len--;
471 buffer[endptr++] = ((char *)buf)[index++];
472 if (endptr == LOOPBACK_BUFFER_SIZE)
473 endptr = 0;
474 while (((startptr - 1) == endptr) || ((endptr==LOOPBACK_BUFFER_SIZE - 1) && (startptr==0))) {
475 usleep(5000);
478 return TRUE;
481 BOOL PSoundChannel::Read(void * buf, PINDEX len)
483 if (!Setup())
484 return FALSE;
486 if (os_handle > 0) {
487 while (!ConvertOSError(::read(os_handle, (void *)buf, len)))
488 if (GetErrorCode() != Interrupted)
489 return FALSE;
490 return TRUE;
493 int index = 0;
495 while (len > 0) {
496 while (startptr == endptr)
497 usleep(5000);
498 len--;
499 ((char *)buf)[index++]=buffer[startptr++];
500 if (startptr == LOOPBACK_BUFFER_SIZE)
501 startptr = 0;
503 return TRUE;
507 BOOL PSoundChannel::SetFormat(unsigned numChannels,
508 unsigned sampleRate,
509 unsigned bitsPerSample)
511 if (os_handle < 0) {
512 lastError = NotOpen;
513 return FALSE;
516 // check parameters
517 PAssert((bitsPerSample == 8) || (bitsPerSample == 16), PInvalidParameter);
518 PAssert(numChannels >= 1 && numChannels <= 2, PInvalidParameter);
520 Abort();
522 // lock the dictionary
523 dictMutex.Wait();
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;
536 // unlock dictionary
537 dictMutex.Signal();
539 // mark this channel as uninitialised
540 isInitialised = FALSE;
542 return TRUE;
546 BOOL PSoundChannel::SetBuffers(PINDEX size, PINDEX count)
548 if (os_handle < 0) {
549 lastError = NotOpen;
550 return FALSE;
553 Abort();
555 PAssert(size > 0 && count > 0 && count < 65536, PInvalidParameter);
556 int arg = 1;
557 while (size > (PINDEX)(1 << arg))
558 arg++;
560 arg |= count << 16;
562 // lock the dictionary
563 dictMutex.Wait();
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;
578 dictMutex.Signal();
580 return TRUE;
584 BOOL PSoundChannel::GetBuffers(PINDEX & size, PINDEX & count)
586 if (os_handle < 0) {
587 lastError = NotOpen;
588 return FALSE;
591 // lock the dictionary
592 dictMutex.Wait();
594 // the device must always be in the dictionary
595 PAssertOS(handleDict().Contains(device));
597 SoundHandleEntry & entry = handleDict()[device];
599 int arg = entry.fragmentValue;
601 dictMutex.Signal();
603 count = arg >> 16;
604 size = 1 << (arg&0xffff);
605 return TRUE;
609 BOOL PSoundChannel::PlaySound(const PSound & sound, BOOL wait)
611 if (os_handle < 0) {
612 lastError = NotOpen;
613 return FALSE;
616 Abort();
618 if (!Write((const BYTE *)sound, sound.GetSize()))
619 return FALSE;
621 if (wait)
622 return WaitForPlayCompletion();
624 return TRUE;
628 BOOL PSoundChannel::PlayFile(const PFilePath & filename, BOOL wait)
630 if (os_handle < 0) {
631 lastError = NotOpen;
632 return FALSE;
635 PFile file(filename, PFile::ReadOnly);
636 if (!file.IsOpen())
637 return FALSE;
639 for (;;) {
640 BYTE buffer[256];
641 if (!file.Read(buffer, 256))
642 break;
643 PINDEX len = file.GetLastReadCount();
644 if (len == 0)
645 break;
646 if (!Write(buffer, len))
647 break;
650 file.Close();
652 if (wait)
653 return WaitForPlayCompletion();
655 return TRUE;
659 BOOL PSoundChannel::HasPlayCompleted()
661 if (os_handle < 0) {
662 lastError = NotOpen;
663 return FALSE;
666 if (os_handle == 0)
667 return BYTESINBUF <= 0;
669 #ifndef P_AIX
670 audio_buf_info info;
671 if (!ConvertOSError(::ioctl(os_handle, SNDCTL_DSP_GETOSPACE, &info)))
672 return FALSE;
674 return info.fragments == info.fragstotal;
675 #else
676 return 0;
677 #endif
682 BOOL PSoundChannel::WaitForPlayCompletion()
684 if (os_handle < 0) {
685 lastError = NotOpen;
686 return FALSE;
689 if (os_handle == 0) {
690 while (BYTESINBUF > 0)
691 usleep(1000);
692 return TRUE;
694 #ifndef P_AIX
695 return ConvertOSError(::ioctl(os_handle, SNDCTL_DSP_SYNC, NULL));
696 #else
697 return 0;
698 #endif
702 BOOL PSoundChannel::RecordSound(PSound & sound)
704 if (os_handle < 0) {
705 lastError = NotOpen;
706 return FALSE;
709 return FALSE;
713 BOOL PSoundChannel::RecordFile(const PFilePath & filename)
715 if (os_handle < 0) {
716 lastError = NotOpen;
717 return FALSE;
720 return FALSE;
724 BOOL PSoundChannel::StartRecording()
726 if (os_handle < 0) {
727 lastError = NotOpen;
728 return FALSE;
731 if (os_handle == 0)
732 return TRUE;
734 fd_set fds;
735 FD_ZERO(&fds);
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()
747 if (os_handle < 0) {
748 lastError = NotOpen;
749 return FALSE;
752 if (os_handle == 0)
753 return (BYTESINBUF > 0);
755 #ifndef P_AIX
756 audio_buf_info info;
757 if (!ConvertOSError(::ioctl(os_handle, SNDCTL_DSP_GETISPACE, &info)))
758 return FALSE;
760 return info.fragments > 0;
761 #else
762 return 0;
763 #endif
767 BOOL PSoundChannel::AreAllRecordBuffersFull()
769 if (os_handle < 0) {
770 lastError = NotOpen;
771 return FALSE;
774 if (os_handle == 0)
775 return (BYTESINBUF == LOOPBACK_BUFFER_SIZE);
777 #ifndef P_AIX
778 audio_buf_info info;
779 if (!ConvertOSError(::ioctl(os_handle, SNDCTL_DSP_GETISPACE, &info)))
780 return FALSE;
782 return info.fragments == info.fragstotal;
783 #else
784 return 0;
785 #endif
789 BOOL PSoundChannel::WaitForRecordBufferFull()
791 if (os_handle < 0) {
792 lastError = NotOpen;
793 return FALSE;
796 return PXSetIOBlock(PXReadBlock, readTimeout);
800 BOOL PSoundChannel::WaitForAllRecordBuffersFull()
802 return FALSE;
806 BOOL PSoundChannel::Abort()
808 if (os_handle == 0) {
809 startptr = endptr = 0;
810 return TRUE;
813 #ifndef P_AIX
814 return ConvertOSError(ioctl(os_handle, SNDCTL_DSP_RESET, NULL));
815 #else
816 return 0;
817 #endif
820 BOOL PSoundChannel::SetVolume(unsigned newVolume)
822 cerr << __FILE__ << "PSoundChannel :: SetVolume called in error. Please fix"<<endl;
823 return FALSE;
826 BOOL PSoundChannel::GetVolume(unsigned & volume)
828 cerr << __FILE__ << "PSoundChannel :: GetVolume called in error. Please fix"<<endl;
829 return FALSE;
834 // End of file