/*
* 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 <phil@netroedge.com>.
*
* $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 <ptlib.h>
#ifdef P_LINUX
#include <sys/soundcard.h>
#include <sys/time.h>
#endif
#ifdef P_FREEBSD
#include <machine/soundcard.h>
#endif
#if defined(P_OPENBSD) || defined(P_NETBSD)
#include <soundcard.h>
#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 ((startptr<endptr)?(endptr-startptr):(LOOPBACK_BUFFER_SIZE+endptr-startptr))
static char buffer[LOOPBACK_BUFFER_SIZE];
static int startptr, endptr;
PMutex PSoundChannel::dictMutex;
static SoundHandleDict & handleDict()
{
static SoundHandleDict dict;
return dict;
}
PSound::PSound(unsigned channels,
unsigned samplesPerSecond,
unsigned bitsPerSample,
PINDEX bufferSize,
const BYTE * buffer)
{
encoding = 0;
numChannels = channels;
sampleRate = samplesPerSecond;
sampleSize = bitsPerSample;
SetSize(bufferSize);
if (buffer != NULL)
memcpy(GetPointer(), buffer, bufferSize);
}
PSound::PSound(const PFilePath & filename)
{
encoding = 0;
numChannels = 1;
sampleRate = 8000;
sampleSize = 16;
Load(filename);
}
PSound & PSound::operator=(const PBYTEArray & data)
{
PBYTEArray::operator=(data);
return *this;
}
void PSound::SetFormat(unsigned channels,
unsigned samplesPerSecond,
unsigned bitsPerSample)
{
encoding = 0;
numChannels = channels;
sampleRate = samplesPerSecond;
sampleSize = bitsPerSample;
formatInfo.SetSize(0);
}
BOOL PSound::Load(const PFilePath & /*filename*/)
{
return FALSE;
}
BOOL PSound::Save(const PFilePath & /*filename*/)
{
return FALSE;
}
BOOL PSound::Play()
{
PSoundChannel channel(PSoundChannel::GetDefaultDevice(PSoundChannel::Player),
PSoundChannel::Player);
if (!channel.IsOpen())
return FALSE;
return channel.PlaySound(*this, TRUE);
}
BOOL PSound::PlayFile(const PFilePath & file, BOOL wait)
{
PSoundChannel channel(PSoundChannel::GetDefaultDevice(PSoundChannel::Player),
PSoundChannel::Player);
if (!channel.IsOpen())
return FALSE;
return channel.PlayFile(file, wait);
}
///////////////////////////////////////////////////////////////////////////////
SoundHandleEntry::SoundHandleEntry()
{
handle = -1;
direction = 0;
}
///////////////////////////////////////////////////////////////////////////////
PSoundChannel::PSoundChannel()
{
Construct();
}
PSoundChannel::PSoundChannel(const PString & device,
Directions dir,
unsigned numChannels,
unsigned sampleRate,
unsigned bitsPerSample)
{
Construct();
Open(device, dir, numChannels, sampleRate, bitsPerSample);
}
void PSoundChannel::Construct()
{
os_handle = -1;
}
PSoundChannel::~PSoundChannel()
{
Close();
}
PStringArray PSoundChannel::GetDeviceNames(Directions /*dir*/)
{
static const char * const devices[] = {
"/dev/audio",
"/dev/dsp",
"/dev/dspW",
"loopback"
};
return PStringArray(PARRAYSIZE(devices), devices);
}
PString PSoundChannel::GetDefaultDevice(Directions /*dir*/)
{
return "/dev/dsp";
}
BOOL PSoundChannel::Open(const PString & _device,
Directions _dir,
unsigned _numChannels,
unsigned _sampleRate,
unsigned _bitsPerSample)
{
Close();
// lock the dictionary
dictMutex.Wait();
// make the direction value 1 or 2
int dir = _dir + 1;
// if this device in in the dictionary
if (handleDict().Contains(_device)) {
SoundHandleEntry & entry = handleDict()[_device];
// see if the sound channel is already open in this direction
if ((entry.direction & dir) != 0) {
dictMutex.Signal();
return FALSE;
}
// flag this entry as open in this direction
entry.direction |= dir;
os_handle = entry.handle;
} else {
// this is the first time this device has been used
// open the device in read/write mode always
if (_device == "loopback") {
startptr = endptr = 0;
os_handle = 0; // Use os_handle value 0 to indicate loopback, cannot ever be stdin!
}
else if (!ConvertOSError(os_handle = ::open((const char *)_device, O_RDWR))) {
dictMutex.Signal();
return FALSE;
}
// add the device to the dictionary
SoundHandleEntry * entry = PNEW SoundHandleEntry;
handleDict().SetAt(_device, entry);
// save the information into the dictionary entry
entry->handle = 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"<<endl;
return FALSE;
}
BOOL PSoundChannel::GetVolume(unsigned & volume)
{
cerr << __FILE__ << "PSoundChannel :: GetVolume called in error. Please fix"<<endl;
return FALSE;
}
// End of file
syntax highlighted by Code2HTML, v. 0.9.1