/* * sound.cxx * * Sound driver implementation. * * Portable Windows Library * * Copyright (c) 1993-1998 Equivalence Pty. Ltd. * * The contents of this file are subject to the Mozilla Public License * Version 1.0 (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See * the License for the specific language governing rights and limitations * under the License. * * The Original Code is Portable Windows Library. * * The Initial Developer of the Original Code is Equivalence Pty. Ltd. * * Portions are Copyright (C) 1993 Free Software Foundation, Inc. * All Rights Reserved. * * Contributor(s): Loopback feature: Philip Edelbrock . * * $Log: ossaix.cxx,v $ * Revision 1.3 2002/02/09 00:52:01 robertj * Slight adjustment to API and documentation for volume functions. * * Revision 1.2 2002/02/07 20:57:21 dereks * add SetVolume and GetVolume methods to PSoundChannel * * Revision 1.1 2000/06/21 01:01:22 robertj * AIX port, thanks Wolfgang Platzer (wolfgang.platzer@infonova.at). * * Revision 1.17 2000/05/11 02:05:54 craigs * Fixed problem with PLayFile not recognizing wait flag * * Revision 1.16 2000/05/10 02:10:44 craigs * Added implementation for PlayFile command * * Revision 1.15 2000/05/02 08:30:26 craigs * Removed "memory leaks" caused by brain-dead GNU linker * * Revision 1.14 2000/04/09 18:19:23 rogerh * Add my changes for NetBSD support. * * Revision 1.13 2000/03/08 12:17:09 rogerh * Add OpenBSD support * * Revision 1.12 2000/03/04 13:02:28 robertj * Added simple play functions for sound files. * * Revision 1.11 2000/02/15 23:11:34 robertj * Audio support for FreeBSD, thanks Roger Hardiman. * * Revision 1.10 2000/01/08 06:41:08 craigs * Fixed problem whereby failure to open sound device returns TRUE * * Revision 1.9 1999/08/24 13:40:26 craigs * Fixed problem with EINTR causing sound channel reads and write to fail * Thanks to phil@netroedge.com! * * Revision 1.8 1999/08/17 09:42:22 robertj * Fixed close of sound channel in loopback mode closing stdin! * * Revision 1.7 1999/08/17 09:28:47 robertj * Added audio loopback psuedo-device (thanks Philip Edelbrock) * * Revision 1.6 1999/07/19 01:31:49 craigs * Major rewrite to assure ioctls are all done in the correct order as OSS seems * to be incredibly sensitive to this. * * Revision 1.5 1999/07/11 13:42:13 craigs * pthreads support for Linux * * Revision 1.4 1999/06/30 13:49:26 craigs * Added code to allow full duplex audio * * Revision 1.3 1999/05/28 14:14:29 robertj * Added function to get default audio device. * * Revision 1.2 1999/05/22 12:49:05 craigs * Finished implementation for Linux OSS interface * * Revision 1.1 1999/02/25 03:45:00 robertj * Sound driver implementation changes for various unix platforms. * * Revision 1.1 1999/02/22 13:24:47 robertj * Added first cut sound implmentation. * */ #pragma implementation "sound.h" #include #ifdef P_LINUX #include #include #endif #ifdef P_FREEBSD #include #endif #if defined(P_OPENBSD) || defined(P_NETBSD) #include #endif /////////////////////////////////////////////////////////////////////////////// // declare type for sound handle dictionary class SoundHandleEntry : public PObject { PCLASSINFO(SoundHandleEntry, PObject) public: SoundHandleEntry(); int handle; int direction; unsigned numChannels; unsigned sampleRate; unsigned bitsPerSample; unsigned fragmentValue; BOOL isInitialised; }; PDICTIONARY(SoundHandleDict, PString, SoundHandleEntry); #define LOOPBACK_BUFFER_SIZE 5000 #define BYTESINBUF ((startptrhandle = os_handle; entry->direction = dir; entry->numChannels = _numChannels; entry->sampleRate = _sampleRate; entry->bitsPerSample = _bitsPerSample; entry->isInitialised = FALSE; entry->fragmentValue = 0x7fff0008; } // unlock the dictionary dictMutex.Signal(); // save the direction and device direction = _dir; device = _device; isInitialised = FALSE; return TRUE; } BOOL PSoundChannel::Setup() { if (os_handle < 0) return FALSE; if (isInitialised) return TRUE; // lock the dictionary dictMutex.Wait(); // the device must always be in the dictionary PAssertOS(handleDict().Contains(device)); // get record for the device SoundHandleEntry & entry = handleDict()[device]; BOOL stat = FALSE; if (entry.isInitialised) { isInitialised = TRUE; stat = TRUE; } else if (device == "loopback") stat = TRUE; else { // must always set paramaters in the following order: // buffer paramaters // sample format (number of bits) // number of channels (mon/stereo) // speed (sampling rate) int arg, val; // reset the device first so it will accept the new parms #ifndef P_AIX if (ConvertOSError(::ioctl(os_handle, SNDCTL_DSP_RESET, &arg))) { arg = val = entry.fragmentValue; //if (ConvertOSError(ioctl(os_handle, SNDCTL_DSP_SETFRAGMENT, &arg)) || (arg != val)) { ::ioctl(os_handle, SNDCTL_DSP_SETFRAGMENT, &arg); { arg = val = (entry.bitsPerSample == 16) ? AFMT_S16_LE : AFMT_S8; if (ConvertOSError(::ioctl(os_handle, SNDCTL_DSP_SETFMT, &arg)) || (arg != val)) { arg = val = (entry.numChannels == 2) ? 1 : 0; if (ConvertOSError(::ioctl(os_handle, SNDCTL_DSP_STEREO, &arg)) || (arg != val)) { arg = val = entry.sampleRate; if (ConvertOSError(::ioctl(os_handle, SNDCTL_DSP_SPEED, &arg)) || (arg != val)) stat = TRUE; } } } } #endif } entry.isInitialised = TRUE; isInitialised = TRUE; dictMutex.Signal(); return stat; } BOOL PSoundChannel::Close() { // if the channel isn't open, do nothing if (os_handle < 0) return TRUE; if (os_handle == 0) { os_handle = -1; return TRUE; } // the device must be in the dictionary dictMutex.Wait(); SoundHandleEntry * entry; PAssert((entry = handleDict().GetAt(device)) != NULL, "Unknown sound device \"" + device + "\" found"); // modify the directions bit mask in the dictionary entry->direction ^= (direction+1); // if this is the last usage of this entry, then remove it if (entry->direction == 0) { handleDict().RemoveAt(device); dictMutex.Signal(); return PChannel::Close(); } // flag this channel as closed dictMutex.Signal(); os_handle = -1; return TRUE; } BOOL PSoundChannel::Write(const void * buf, PINDEX len) { if (!Setup()) return FALSE; if (os_handle > 0) { while (!ConvertOSError(::write(os_handle, (void *)buf, len))) if (GetErrorCode() != Interrupted) return FALSE; return TRUE; } int index = 0; while (len > 0) { len--; buffer[endptr++] = ((char *)buf)[index++]; if (endptr == LOOPBACK_BUFFER_SIZE) endptr = 0; while (((startptr - 1) == endptr) || ((endptr==LOOPBACK_BUFFER_SIZE - 1) && (startptr==0))) { usleep(5000); } } return TRUE; } BOOL PSoundChannel::Read(void * buf, PINDEX len) { if (!Setup()) return FALSE; if (os_handle > 0) { while (!ConvertOSError(::read(os_handle, (void *)buf, len))) if (GetErrorCode() != Interrupted) return FALSE; return TRUE; } int index = 0; while (len > 0) { while (startptr == endptr) usleep(5000); len--; ((char *)buf)[index++]=buffer[startptr++]; if (startptr == LOOPBACK_BUFFER_SIZE) startptr = 0; } return TRUE; } BOOL PSoundChannel::SetFormat(unsigned numChannels, unsigned sampleRate, unsigned bitsPerSample) { if (os_handle < 0) { lastError = NotOpen; return FALSE; } // check parameters PAssert((bitsPerSample == 8) || (bitsPerSample == 16), PInvalidParameter); PAssert(numChannels >= 1 && numChannels <= 2, PInvalidParameter); Abort(); // lock the dictionary dictMutex.Wait(); // the device must always be in the dictionary PAssertOS(handleDict().Contains(device)); // get record for the device SoundHandleEntry & entry = handleDict()[device]; entry.numChannels = numChannels; entry.sampleRate = sampleRate; entry.bitsPerSample = bitsPerSample; entry.isInitialised = FALSE; // unlock dictionary dictMutex.Signal(); // mark this channel as uninitialised isInitialised = FALSE; return TRUE; } BOOL PSoundChannel::SetBuffers(PINDEX size, PINDEX count) { if (os_handle < 0) { lastError = NotOpen; return FALSE; } Abort(); PAssert(size > 0 && count > 0 && count < 65536, PInvalidParameter); int arg = 1; while (size > (PINDEX)(1 << arg)) arg++; arg |= count << 16; // lock the dictionary dictMutex.Wait(); // the device must always be in the dictionary PAssertOS(handleDict().Contains(device)); // get record for the device SoundHandleEntry & entry = handleDict()[device]; // set information in the common record entry.fragmentValue = arg; entry.isInitialised = FALSE; // flag this channel as not initialised isInitialised = FALSE; dictMutex.Signal(); return TRUE; } BOOL PSoundChannel::GetBuffers(PINDEX & size, PINDEX & count) { if (os_handle < 0) { lastError = NotOpen; return FALSE; } // lock the dictionary dictMutex.Wait(); // the device must always be in the dictionary PAssertOS(handleDict().Contains(device)); SoundHandleEntry & entry = handleDict()[device]; int arg = entry.fragmentValue; dictMutex.Signal(); count = arg >> 16; size = 1 << (arg&0xffff); return TRUE; } BOOL PSoundChannel::PlaySound(const PSound & sound, BOOL wait) { if (os_handle < 0) { lastError = NotOpen; return FALSE; } Abort(); if (!Write((const BYTE *)sound, sound.GetSize())) return FALSE; if (wait) return WaitForPlayCompletion(); return TRUE; } BOOL PSoundChannel::PlayFile(const PFilePath & filename, BOOL wait) { if (os_handle < 0) { lastError = NotOpen; return FALSE; } PFile file(filename, PFile::ReadOnly); if (!file.IsOpen()) return FALSE; for (;;) { BYTE buffer[256]; if (!file.Read(buffer, 256)) break; PINDEX len = file.GetLastReadCount(); if (len == 0) break; if (!Write(buffer, len)) break; } file.Close(); if (wait) return WaitForPlayCompletion(); return TRUE; } BOOL PSoundChannel::HasPlayCompleted() { if (os_handle < 0) { lastError = NotOpen; return FALSE; } if (os_handle == 0) return BYTESINBUF <= 0; #ifndef P_AIX audio_buf_info info; if (!ConvertOSError(::ioctl(os_handle, SNDCTL_DSP_GETOSPACE, &info))) return FALSE; return info.fragments == info.fragstotal; #else return 0; #endif } BOOL PSoundChannel::WaitForPlayCompletion() { if (os_handle < 0) { lastError = NotOpen; return FALSE; } if (os_handle == 0) { while (BYTESINBUF > 0) usleep(1000); return TRUE; } #ifndef P_AIX return ConvertOSError(::ioctl(os_handle, SNDCTL_DSP_SYNC, NULL)); #else return 0; #endif } BOOL PSoundChannel::RecordSound(PSound & sound) { if (os_handle < 0) { lastError = NotOpen; return FALSE; } return FALSE; } BOOL PSoundChannel::RecordFile(const PFilePath & filename) { if (os_handle < 0) { lastError = NotOpen; return FALSE; } return FALSE; } BOOL PSoundChannel::StartRecording() { if (os_handle < 0) { lastError = NotOpen; return FALSE; } if (os_handle == 0) return TRUE; fd_set fds; FD_ZERO(&fds); FD_SET(os_handle, &fds); struct timeval timeout; memset(&timeout, 0, sizeof(timeout)); return ConvertOSError(::select(1, &fds, NULL, NULL, &timeout)); } BOOL PSoundChannel::IsRecordBufferFull() { if (os_handle < 0) { lastError = NotOpen; return FALSE; } if (os_handle == 0) return (BYTESINBUF > 0); #ifndef P_AIX audio_buf_info info; if (!ConvertOSError(::ioctl(os_handle, SNDCTL_DSP_GETISPACE, &info))) return FALSE; return info.fragments > 0; #else return 0; #endif } BOOL PSoundChannel::AreAllRecordBuffersFull() { if (os_handle < 0) { lastError = NotOpen; return FALSE; } if (os_handle == 0) return (BYTESINBUF == LOOPBACK_BUFFER_SIZE); #ifndef P_AIX audio_buf_info info; if (!ConvertOSError(::ioctl(os_handle, SNDCTL_DSP_GETISPACE, &info))) return FALSE; return info.fragments == info.fragstotal; #else return 0; #endif } BOOL PSoundChannel::WaitForRecordBufferFull() { if (os_handle < 0) { lastError = NotOpen; return FALSE; } return PXSetIOBlock(PXReadBlock, readTimeout); } BOOL PSoundChannel::WaitForAllRecordBuffersFull() { return FALSE; } BOOL PSoundChannel::Abort() { if (os_handle == 0) { startptr = endptr = 0; return TRUE; } #ifndef P_AIX return ConvertOSError(ioctl(os_handle, SNDCTL_DSP_RESET, NULL)); #else return 0; #endif } BOOL PSoundChannel::SetVolume(unsigned newVolume) { cerr << __FILE__ << "PSoundChannel :: SetVolume called in error. Please fix"<