/*
* 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: sound_oss.cxx,v $
* Revision 1.9.2.1 2006/04/23 18:19:10 dsandras
* Backport from HEAD.
*
* Revision 1.10 2006/04/23 18:16:59 dsandras
* Fixed OSS plugin when there is no resampling.
*
* Revision 1.9 2005/11/30 12:47:38 csoutheren
* Removed tabs, reformatted some code, and changed tags for Doxygen
*
* Revision 1.8 2005/08/14 12:58:58 csoutheren
* Fixed build problem on 64bit Linux
*
* Revision 1.7 2004/11/15 13:26:43 csoutheren
* Removed debugging (oops!)
*
* Revision 1.6 2004/11/08 04:04:51 csoutheren
* Added resampling to allow operation on hardware that only supports 48khz
*
* Revision 1.5 2004/11/07 20:01:31 dsandras
* Make sure lastWriteCount is updated.
*
* Revision 1.4 2003/11/18 10:59:06 csoutheren
* Removed ALSA compatibility hack as ALSA users can use the ALSA plugin
*
* Revision 1.3 2003/11/14 05:58:00 csoutheren
* Removed loopback code as this should be in a seperate plugin :)
*
* Revision 1.2 2003/11/12 03:24:15 csoutheren
* Imported plugin code from crs_pwlib_plugin branch and combined with
* new plugin code from Snark of GnomeMeeting
*
* Revision 1.1.2.1 2003/10/07 01:36:51 csoutheren
* nitial checkin of pwlib code to do plugins.
* Modified from original code and concept provided by Snark of Gnomemeeting
*
* Revision 1.59 2003/05/24 10:57:05 rogerhardiman
* If a sound device cannot be opened in RW mode, try just 'R' or 'W' modes.
* Needed for USB web cams with Mics which are read only sound devices when using
* ALSA. Tested by Damien.
*
* Revision 1.58 2003/02/02 18:54:22 rogerh
* FreeBSD changes for support of dspN.M (eg dsp0.0) sound card entries.
* Problem reported by Lars Eggert <larse@isi.edu>
*
* Revision 1.57 2003/01/06 19:25:07 rogerh
* Add NetBSD video support.
* Add correct includes for OSS ioctls (note the proper way to do this now
* is via pwlib commands)
* From Andreas Wrede, taken in part from NetBSD's package system
*
* Revision 1.56 2003/01/06 19:10:22 rogerh
* NetBSD uses /dev/audio and not /dev/dsp
*
* Revision 1.55 2002/12/12 09:03:56 rogerh
* On two FreeBSD machines, Read() calls from the sound card were not blocking
* correctly and returned with less bytes than asked for. This made OpenH323
* close the sound channel. Add a FreeBSD workaround so Read() loops until it
* has all the bytes requested.
*
* Revision 1.54 2002/12/03 23:03:54 rogerh
* oops - remove some test code which should not have been committed
*
* Revision 1.53 2002/12/03 19:11:58 rogerh
* Open sound device in non blocking mode incase it is already open.
*
* Revision 1.52 2002/11/28 12:15:24 rogerh
* Change SetVolume/GetVolume to use the mic and not the igain for the input
* volume.
* Our target audience are likely to be using mics and many broken
* sound drivers do not implement igain properly.
*
* Revision 1.51 2002/10/17 12:57:24 robertj
* Added ability to increase maximum file handles on a process.
*
* Revision 1.50 2002/10/15 10:42:45 rogerh
* Fix loopback mode, which was broken in a recent change.
*
* Revision 1.49 2002/09/29 16:19:28 rogerh
* if /dev/dsp does not exist, do not return it as the default audio device.
* Instead, return the first dsp device.
*
* Revision 1.48 2002/09/29 15:56:49 rogerh
* Revert back to checking for the /dev/soundcard directory to detect devfs.
* If seems that the .devfsd file is not removed when devfs is not being used.
*
* Revision 1.47 2002/09/29 09:26:16 rogerh
* Changes to sound card detection.
* For Damien Sandras, allow /dev/dsp to be added to the list of sound devices
* For FreeBSD, ignore /dev/dspN.M eg /dev/dsp0.2 which are virtual soundcards
*
* Revision 1.46 2002/08/30 07:58:27 craigs
* Added fix for when sound cards are already open, thanks to Damien Sandras
*
* Revision 1.45 2002/08/15 19:57:38 rogerh
* Linux defvs mode is detected with /dev/.devfsd
*
* Revision 1.44 2002/07/04 05:00:36 robertj
* Fixed order of calls for OSS driver setup.
*
* Revision 1.43 2002/06/24 20:01:53 rogerh
* Add support for linux devfs and /dev/sound. Based on code by Snark.
*
* Revision 1.42 2002/06/09 16:33:45 rogerh
* Use new location of soundcard.h on FreeBSD
*
* Revision 1.41 2002/06/05 12:29:15 craigs
* Changes for gcc 3.1
*
* Revision 1.40 2002/05/02 14:19:32 rogerh
* Handle Big Endian systems correctly.
* Patch submitted by andi@fischlustig.de
*
* Revision 1.39 2002/02/11 07:21:46 rogerh
* Fix some non portable code which which seeks out /dev/dsp devices and only
* worked on Linux. (char device for /dev/dsp is 14 on Linux, 30 on FreeBSD)
*
* Revision 1.38 2002/02/09 00:52:01 robertj
* Slight adjustment to API and documentation for volume functions.
*
* Revision 1.37 2002/02/07 20:57:21 dereks
* add SetVolume and GetVolume methods to PSoundChannel
*
* Revision 1.36 2002/01/24 05:57:38 rogerh
* Fix warning
*
* Revision 1.35 2002/01/24 05:55:52 rogerh
* fill lastReadCount (in the base class) when doing a Read.
*
* Revision 1.34 2002/01/07 04:15:38 robertj
* Removed ALSA major device number as this is not how it does its OSS
* compatibility mode, it uses device id 14 as usual.
*
* Revision 1.33 2001/12/08 00:58:41 robertj
* Added ability to stil work with strange sound card setup, thanks Damian Sandras.
*
* Revision 1.32 2001/09/18 05:56:03 robertj
* Fixed numerous problems with thread suspend/resume and signals handling.
*
* Revision 1.31 2001/09/14 05:10:57 robertj
* Fixed compatibility issue with FreeBSD versionof OSS.
*
* Revision 1.30 2001/09/14 04:53:04 robertj
* Improved the detection of sound cards, thanks Miguel Rodríguez Pérez for the ideas.
*
* Revision 1.29 2001/09/10 03:03:36 robertj
* Major change to fix problem with error codes being corrupted in a
* PChannel when have simultaneous reads and writes in threads.
*
* Revision 1.28 2001/09/03 09:15:40 robertj
* Changed GetDeviceNames to try and find actual devices and real devices.
*
* Revision 1.27 2001/08/22 02:23:07 robertj
* Fixed duplicate class name. All PWlib classes should start with P
*
* Revision 1.26 2001/08/21 12:33:25 rogerh
* Make loopback mode actually work. Added the AudioDelay class from OpenMCU
* and made Read() return silence when the buffer is empty.
*
* Revision 1.25 2001/05/14 06:33:19 rogerh
* Add exit cases to loopback mode polling loops to allow the sound channel
* to close properly when a connection closes.
*
* Revision 1.24 2001/02/07 03:34:29 craigs
* Added ability get sound channel parameters
*
* Revision 1.23 2000/10/05 00:04:20 robertj
* Fixed some warnings.
*
* Revision 1.22 2000/07/04 20:34:16 rogerh
* Only use ioctl SNDCTL_DSP_SETDUPLEX is Linux. It is not defined in FreeBSD
* In NetBSD and OpenBSD (using liboss), the ioctl returns EINVAL.
*
* Revision 1.21 2000/07/02 14:18:27 craigs
* Fixed various problems with buffer handling
*
* Revision 1.20 2000/07/02 05:49:43 craigs
* Really fixed race condition in OSS open
*
* Revision 1.19 2000/07/02 04:55:18 craigs
* Fixed stupid mistake with fix for OSS race condition
*
* Revision 1.18 2000/07/02 04:50:44 craigs
* Fixed potential race condition in OSS initialise
*
* 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_oss.h"
#include "sound_oss.h"
PCREATE_SOUND_PLUGIN(OSS, PSoundChannelOSS);
///////////////////////////////////////////////////////////////////////////////
// declare type for sound handle dictionary
PDICTIONARY(SoundHandleDict, PString, SoundHandleEntry);
static PMutex dictMutex;
static SoundHandleDict & handleDict()
{
static SoundHandleDict dict;
return dict;
}
///////////////////////////////////////////////////////////////////////////////
SoundHandleEntry::SoundHandleEntry()
{
handle = -1;
direction = 0;
}
///////////////////////////////////////////////////////////////////////////////
PSoundChannelOSS::PSoundChannelOSS()
{
PSoundChannelOSS::Construct();
}
PSoundChannelOSS::PSoundChannelOSS(const PString & device,
Directions dir,
unsigned numChannels,
unsigned sampleRate,
unsigned bitsPerSample)
{
Construct();
Open(device, dir, numChannels, sampleRate, bitsPerSample);
}
void PSoundChannelOSS::Construct()
{
os_handle = -1;
}
PSoundChannelOSS::~PSoundChannelOSS()
{
Close();
}
static BOOL IsNumericString(PString numbers) {
// return true if 'numbers' contains only digits (0 to 9)
// or if it contains digits followed by a '.'
BOOL isNumber = FALSE;
for (PINDEX p = 0; p < numbers.GetLength(); p++) {
if (isdigit(numbers[p])) {
isNumber = TRUE;
} else {
return isNumber;
}
}
return isNumber;
}
static void CollectSoundDevices(PDirectory devdir, POrdinalToString & dsp, POrdinalToString & mixer, BOOL collect_with_names)
{
if (!devdir.Open())
return;
do {
PString filename = devdir.GetEntryName();
PString devname = devdir + filename;
if (devdir.IsSubDir())
CollectSoundDevices(devname, dsp, mixer, collect_with_names);
else {
if (!collect_with_names) {
// On Linux, look at the character device numbers
PFileInfo info;
if (devdir.GetInfo(info) &&info.type == PFileInfo::CharDevice) {
struct stat s;
if (lstat(devname, &s) == 0) {
// OSS compatible audio major device numbers (OSS, SAM9407, etc)
static const unsigned deviceNumbers[] = { 14, 145 };
for (PINDEX i = 0; i < PARRAYSIZE(deviceNumbers); i++) {
if ((s.st_rdev >> 8) == deviceNumbers[i]) {
PINDEX cardnum = (s.st_rdev >> 4) & 15;
if ((s.st_rdev & 15) == 3) // Digital audio minor device number
dsp.SetAt(cardnum, devname);
else if ((s.st_rdev & 15) == 0) // Digital audio minor device number
mixer.SetAt(cardnum, devname);
}
}
}
}
}
else {
// On Linux devfs systems, the major numbers can change dynamically.
// On FreeBSD and other OSs, the major numbes are different to Linux.
// So collect devices by looking for dsp(N) and mixer(N).
// (or /dev/audio(N) and mixer(N) on NetBSD
// Notes. FreeBSD supports audio stream mixing. A single sound card
// may have multiple /dev entries in the form /dev/dspN.M
// eg /dev/dsp0.0 /dev/dsp0.1 /dev/dsp0.2 and /dev/dsp0.3
// When adding these to the 'dsp' string array, only the first one
// found is used.
#ifndef P_NETBSD
// Look for dsp
if (filename == "dsp") {
dsp.SetAt(0, devname);
}
// Look for dspN entries. Insert at position N + 1
// and look for dspN.M entries. Insert at position N + 1 (ignoring M)
if ((filename.GetLength() > 3) && (filename.Left(3) == "dsp")) {
PString numbers = filename.Mid(3); // get everything after 'dsp'
if (IsNumericString(numbers)) {
PINDEX cardnum = numbers.AsInteger(); //dspN.M is truncated to dspN.
// If we have not yet inserted something for this cardnum, insert it
if (dsp.GetAt(cardnum+1) == NULL) {
dsp.SetAt(cardnum+1, devname);
}
}
}
#else
// Look for audio on NetBSD
if (filename == "audio") {
dsp.SetAt(0, devname);
}
// Look for audioN. Insert at position cardnum + 1
if ((filename.GetLength() > 5) && (filename.Left(5) == "audio")) {
PString numbers = filename.Mid(5); // get everything after 'audio'
if (IsNumericString(numbers)) {
PINDEX cardnum = numbers.AsInteger();
dsp.SetAt(cardnum+1, devname);
}
}
#endif
// Look for mixer
if (filename == "mixer") {
mixer.SetAt(0, devname);
}
// Look for mixerN. Insert at position cardnum + 1
if ((filename.GetLength() > 5) && (filename.Left(5) == "mixer")) {
PString numbers = filename.Mid(5); // get everything after 'mixer'
if (IsNumericString(numbers)) {
PINDEX cardnum = numbers.AsInteger();
mixer.SetAt(cardnum+1, devname);
}
}
}
}
} while (devdir.Next());
}
PStringArray PSoundChannelOSS::GetDeviceNames(Directions /*dir*/)
{
// First locate sound cards. On Linux with devfs and on the other platforms
// (eg FreeBSD), we search for filenames with dspN or mixerN.
// On linux without devfs we scan all of the devices and look for ones
// with major device numbers corresponding to OSS compatible drivers.
POrdinalToString dsp, mixer;
#ifdef P_LINUX
PDirectory devdir = "/dev/sound";
if (devdir.Open()) {
CollectSoundDevices("/dev/sound", dsp, mixer, TRUE); // use names (devfs)
} else {
CollectSoundDevices("/dev", dsp, mixer, FALSE); // use major numbers
}
#else
CollectSoundDevices("/dev", dsp, mixer, TRUE); // use names
#endif
// Now we go through the collected devices and see if any have a phyisical reality
PStringList devices;
for (PINDEX i = 0; i < dsp.GetSize(); i++) {
PINDEX cardnum = dsp.GetKeyAt(i);
// Try and open mixer if have one as this is unlikely to fail for any
// reason other than there not being a physical device
if (mixer.Contains(cardnum)) {
int fd = ::open(mixer[cardnum], O_RDONLY);
if (fd >= 0) {
// Do something with the mixer to be sure it is there
int dummy;
if (::ioctl(fd, SOUND_MIXER_READ_DEVMASK, &dummy) >= 0)
devices.AppendString(dsp[cardnum]);
::close(fd);
}
else {
// mixer failed but this could still be a valid dsp...
// warning this is just a hack to make this work on strange mixer and dsp configurations
// on my machine the first sound card registers 1 mixer and 2 dsp, so when my webcam
// registers itself as dsp2 this test would fail...
int fd = ::open(dsp[cardnum], O_RDONLY | O_NONBLOCK);
if (fd >= 0 || errno == EBUSY) {
devices.AppendString(dsp[cardnum]);
::close(fd);
}
}
}
else {
// No mixer available, try and open it directly, this could fail if
// the device happens to be open already
int fd = ::open(dsp[cardnum], O_RDONLY | O_NONBLOCK);
if (fd >= 0 || errno == EBUSY) {
devices.AppendString(dsp[cardnum]);
::close(fd);
}
}
}
return devices;
}
PString PSoundChannelOSS::GetDefaultDevice(Directions dir)
{
// Normally /dev/dsp points to the default sound device. If this is not
// present, probe /dev for sound devices and return the first detected device.
// return the first dsp device detected
PStringArray devicenames;
devicenames = PSoundChannelOSS::GetDeviceNames(dir);
return devicenames[0];
}
BOOL PSoundChannelOSS::Open(const PString & _device,
Directions _dir,
unsigned _numChannels,
unsigned _sampleRate,
unsigned _bitsPerSample)
{
Close();
// lock the dictionary
PWaitAndSignal mutex(dictMutex);
// make the direction value 1 or 2
int dir = _dir + 1;
// if this device is 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) {
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
// open the device in non-blocking mode to avoid hang if already open
os_handle = ::open((const char *)_device, O_RDWR | O_NONBLOCK);
if ((os_handle < 0) && (errno != EWOULDBLOCK))
return ConvertOSError(os_handle);
// switch to blocking mode
DWORD cmd = 0;
::ioctl(os_handle, FIONBIO, &cmd);
// 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 = mNumChannels = _numChannels;
entry->sampleRate = actualSampleRate = mSampleRate = _sampleRate;
entry->bitsPerSample = mBitsPerSample = _bitsPerSample;
entry->isInitialised = FALSE;
entry->fragmentValue = 0x7fff0008;
entry->resampleRate = 0;
}
// save the direction and device
direction = _dir;
device = _device;
isInitialised = FALSE;
return TRUE;
}
BOOL PSoundChannelOSS::Setup()
{
PWaitAndSignal mutex(dictMutex);
if (os_handle < 0) {
PTRACE(6, "OSS\tSkipping setup of " << device << " as not open");
return FALSE;
}
if (isInitialised) {
PTRACE(6, "OSS\tSkipping setup of " << device << " as instance already initialised");
return TRUE;
}
// the device must always be in the dictionary
PAssertOS(handleDict().Contains(device));
// get record for the device
SoundHandleEntry & entry = handleDict()[device];
// set default return status
BOOL stat = TRUE;
// do not re-initialise initialised devices
if (entry.isInitialised) {
PTRACE(6, "OSS\tSkipping setup for " << device << " as already initialised");
resampleRate = entry.resampleRate;
} else {
PTRACE(6, "OSS\tInitialising " << device << "(" << (void *)(&entry) << ")");
#if defined(P_LINUX)
// enable full duplex (maybe).
::ioctl(os_handle, SNDCTL_DSP_SETDUPLEX, 0);
#endif
stat = FALSE;
// 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
if (ConvertOSError(::ioctl(os_handle, SNDCTL_DSP_RESET, &arg))) {
// set the write fragment size (applies to sound output only)
arg = val = entry.fragmentValue;
::ioctl(os_handle, SNDCTL_DSP_SETFRAGMENT, &arg);
mBitsPerSample = entry.bitsPerSample;
#if PBYTE_ORDER == PLITTLE_ENDIAN
arg = val = (entry.bitsPerSample == 16) ? AFMT_S16_LE : AFMT_S8;
#else
arg = val = (entry.bitsPerSample == 16) ? AFMT_S16_BE : AFMT_S8;
#endif
if (ConvertOSError(::ioctl(os_handle, SNDCTL_DSP_SETFMT, &arg)) || (arg != val)) {
mNumChannels = entry.numChannels;
arg = val = (entry.numChannels == 2) ? 1 : 0;
if (ConvertOSError(::ioctl(os_handle, SNDCTL_DSP_STEREO, &arg)) || (arg != val)) {
mSampleRate = entry.sampleRate;
arg = val = entry.sampleRate;
if (ConvertOSError(::ioctl(os_handle, SNDCTL_DSP_SPEED, &arg))) {
stat = TRUE;
// detect cases where the hardware can't do the actual rate we need, but can do a simple multiple
if (arg != (int)entry.sampleRate) {
if (((arg / entry.sampleRate) * entry.sampleRate) == (unsigned)arg) {
PTRACE(3, "Resampling data at " << entry.sampleRate << " to match hardware rate of " << arg);
resampleRate = entry.resampleRate = arg / entry.sampleRate;
} else {
PTRACE_IF(4, actualSampleRate != (unsigned)val, "Actual sample rate selected is " << actualSampleRate << ", not " << entry.sampleRate);
actualSampleRate = arg;
}
}
}
}
}
#if PTRACING
audio_buf_info info;
::ioctl(os_handle, SNDCTL_DSP_GETOSPACE, &info);
PTRACE(4, "OSS\tOutput: fragments = " << info.fragments
<< ", total frags = " << info.fragstotal
<< ", frag size = " << info.fragsize
<< ", bytes = " << info.bytes);
::ioctl(os_handle, SNDCTL_DSP_GETISPACE, &info);
PTRACE(4, "OSS\tInput: fragments = " << info.fragments
<< ", total frags = " << info.fragstotal
<< ", frag size = " << info.fragsize
<< ", bytes = " << info.bytes);
#endif
}
}
// ensure device is marked as initialised
isInitialised = TRUE;
entry.isInitialised = TRUE;
return stat;
}
BOOL PSoundChannelOSS::Close()
{
// if the channel isn't open, do nothing
if (os_handle < 0)
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 PSoundChannelOSS::IsOpen() const
{
return os_handle >= 0;
}
BOOL PSoundChannelOSS::Write(const void * buf, PINDEX len)
{
lastWriteCount = 0;
if (!Setup() || os_handle < 0)
return FALSE;
if (resampleRate == 0) {
while (!ConvertOSError(::write(os_handle, (void *)buf, len)))
if (GetErrorCode() != Interrupted)
return FALSE;
lastWriteCount += len;
}
else {
// cut the data into 1K blocks and upsample it
lastWriteCount = 0;
BYTE resampleBuffer[1024];
const BYTE * src = (const BYTE *)buf;
const BYTE * srcEnd = src + len;
while (src < srcEnd) {
// expand the data by the appropriate sample ratio
BYTE * dst = resampleBuffer;
const BYTE * srcStart = src;
unsigned j;
while ((src < srcEnd) && (dst < (resampleBuffer + sizeof(resampleBuffer) - resampleRate*2))) {
for (j = 0; j < resampleRate; ++j) {
memcpy(dst, src, 2);
dst += 2 ;
}
src += 2;
}
lastWriteCount += src - srcStart;
while (!ConvertOSError(::write(os_handle, resampleBuffer, dst - resampleBuffer))) {
if (GetErrorCode() != Interrupted)
return FALSE;
}
}
}
return TRUE;
}
BOOL PSoundChannelOSS::Read(void * buf, PINDEX len)
{
lastReadCount = 0;
if (!Setup() || os_handle < 0)
return FALSE;
if (resampleRate == 0) {
PINDEX total = 0;
while (total < len) {
PINDEX bytes = 0;
while (!ConvertOSError(bytes = ::read(os_handle, (void *)(((unsigned char *)buf) + total), len-total))) {
if (GetErrorCode() != Interrupted) {
PTRACE(6, "OSS\tRead failed");
return FALSE;
}
PTRACE(6, "OSS\tRead interrupted");
}
total += bytes;
if (total != len)
PTRACE(6, "OSS\tRead completed short - " << total << " vs " << len << ". Reading more data");
}
lastReadCount = total;
}
else {
// downsample the data
BYTE * dst = (BYTE *)buf;
BYTE * dstEnd = dst + len;
lastReadCount = 0;
PBYTEArray resampleBuffer((1024 / resampleRate) * resampleRate);
// downsample the data into 1K blocks
while (dst < dstEnd) {
// calculate number of source bytes needed to fill the buffer
PINDEX srcBytes = resampleRate * (dstEnd - dst);
PINDEX bytes;
{
PINDEX bufLen = PMIN(resampleBuffer.GetSize(), srcBytes);
while (!ConvertOSError(bytes = ::read(os_handle, resampleBuffer.GetPointer(), bufLen))) {
if (GetErrorCode() != Interrupted)
return FALSE;
}
}
// use an average, not just a single sample
const BYTE * src = resampleBuffer;
while ( ((src - resampleBuffer) < bytes) && (dst < dstEnd)) {
int sample = 0;
unsigned j;
for (j = 0; j < resampleRate; ++j) {
sample += *(PUInt16l *)src;
src += 2;
}
*(PUInt16l *)dst = sample / resampleRate;
dst +=2 ;
lastReadCount += 2;
}
}
}
if (lastReadCount != len)
PTRACE(6, "OSS\tRead completed short - " << lastReadCount << " vs " << len);
else
PTRACE(6, "OSS\tRead completed");
return TRUE;
}
BOOL PSoundChannelOSS::SetFormat(unsigned numChannels,
unsigned sampleRate,
unsigned bitsPerSample)
{
if (os_handle < 0)
return SetErrorValues(NotOpen, EBADF);
// check parameters
PAssert((bitsPerSample == 8) || (bitsPerSample == 16), PInvalidParameter);
PAssert(numChannels >= 1 && numChannels <= 2, PInvalidParameter);
// lock the dictionary
PWaitAndSignal mutex(dictMutex);
// the device must always be in the dictionary
PAssertOS(handleDict().Contains(device));
// get record for the device
SoundHandleEntry & entry = handleDict()[device];
if (entry.isInitialised) {
if ((numChannels != entry.numChannels) ||
(sampleRate != entry.sampleRate) ||
(bitsPerSample != entry.bitsPerSample)) {
PTRACE(6, "OSS\tTried to change read/write format without stopping");
return FALSE;
}
return TRUE;
}
Abort();
entry.numChannels = numChannels;
entry.sampleRate = sampleRate;
entry.bitsPerSample = bitsPerSample;
entry.isInitialised = FALSE;
// mark this channel as uninitialised
isInitialised = FALSE;
return TRUE;
}
// Get the number of channels (mono/stereo) in the sound.
unsigned PSoundChannelOSS::GetChannels() const
{
return mNumChannels;
}
// Get the sample rate in samples per second.
unsigned PSoundChannelOSS::GetSampleRate() const
{
return actualSampleRate;
}
// Get the sample size in bits per sample.
unsigned PSoundChannelOSS::GetSampleSize() const
{
return mBitsPerSample;
}
BOOL PSoundChannelOSS::SetBuffers(PINDEX size, PINDEX count)
{
if (os_handle < 0)
return SetErrorValues(NotOpen, EBADF);
//PINDEX totalSize = size * count;
//size = 16;
//count = (totalSize + 15) / 16;
PAssert(size > 0 && count > 0 && count < 65536, PInvalidParameter);
int arg = 1;
while (size > (PINDEX)(1 << arg))
arg++;
arg |= count << 16;
// lock the dictionary
PWaitAndSignal mutex(dictMutex);
// the device must always be in the dictionary
PAssertOS(handleDict().Contains(device));
// get record for the device
SoundHandleEntry & entry = handleDict()[device];
if (entry.isInitialised) {
if (entry.fragmentValue != (unsigned)arg) {
PTRACE(6, "OSS\tTried to change buffers without stopping");
return FALSE;
}
return TRUE;
}
Abort();
// set information in the common record
entry.fragmentValue = arg;
entry.isInitialised = FALSE;
// flag this channel as not initialised
isInitialised = FALSE;
return TRUE;
}
BOOL PSoundChannelOSS::GetBuffers(PINDEX & size, PINDEX & count)
{
if (os_handle < 0)
return SetErrorValues(NotOpen, EBADF);
// lock the dictionary
PWaitAndSignal mutex(dictMutex);
// the device must always be in the dictionary
PAssertOS(handleDict().Contains(device));
SoundHandleEntry & entry = handleDict()[device];
int arg = entry.fragmentValue;
count = arg >> 16;
size = 1 << (arg&0xffff);
return TRUE;
}
BOOL PSoundChannelOSS::PlaySound(const PSound & sound, BOOL wait)
{
if (os_handle < 0)
return SetErrorValues(NotOpen, EBADF);
Abort();
if (!Write((const BYTE *)sound, sound.GetSize()))
return FALSE;
if (wait)
return WaitForPlayCompletion();
return TRUE;
}
BOOL PSoundChannelOSS::PlayFile(const PFilePath & filename, BOOL wait)
{
if (os_handle < 0)
return SetErrorValues(NotOpen, EBADF);
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 PSoundChannelOSS::HasPlayCompleted()
{
if (os_handle < 0)
return SetErrorValues(NotOpen, EBADF);
audio_buf_info info;
if (!ConvertOSError(::ioctl(os_handle, SNDCTL_DSP_GETOSPACE, &info)))
return FALSE;
return info.fragments == info.fragstotal;
}
BOOL PSoundChannelOSS::WaitForPlayCompletion()
{
if (os_handle < 0)
return SetErrorValues(NotOpen, EBADF);
return ConvertOSError(::ioctl(os_handle, SNDCTL_DSP_SYNC, NULL));
}
BOOL PSoundChannelOSS::RecordSound(PSound & sound)
{
if (os_handle < 0)
return SetErrorValues(NotOpen, EBADF);
return FALSE;
}
BOOL PSoundChannelOSS::RecordFile(const PFilePath & filename)
{
if (os_handle < 0)
return SetErrorValues(NotOpen, EBADF);
return FALSE;
}
BOOL PSoundChannelOSS::StartRecording()
{
if (os_handle < 0)
return SetErrorValues(NotOpen, EBADF);
if (os_handle == 0)
return TRUE;
P_fd_set fds = os_handle;
P_timeval instant;
return ConvertOSError(::select(1, fds, NULL, NULL, instant));
}
BOOL PSoundChannelOSS::IsRecordBufferFull()
{
if (os_handle < 0)
return SetErrorValues(NotOpen, EBADF);
audio_buf_info info;
if (!ConvertOSError(::ioctl(os_handle, SNDCTL_DSP_GETISPACE, &info)))
return FALSE;
return info.fragments > 0;
}
BOOL PSoundChannelOSS::AreAllRecordBuffersFull()
{
if (os_handle < 0)
return SetErrorValues(NotOpen, EBADF);
audio_buf_info info;
if (!ConvertOSError(::ioctl(os_handle, SNDCTL_DSP_GETISPACE, &info)))
return FALSE;
return info.fragments == info.fragstotal;
}
BOOL PSoundChannelOSS::WaitForRecordBufferFull()
{
if (os_handle < 0)
return SetErrorValues(NotOpen, EBADF);
return PXSetIOBlock(PXReadBlock, readTimeout);
}
BOOL PSoundChannelOSS::WaitForAllRecordBuffersFull()
{
return FALSE;
}
BOOL PSoundChannelOSS::Abort()
{
return ConvertOSError(ioctl(os_handle, SNDCTL_DSP_RESET, NULL));
}
BOOL PSoundChannelOSS::SetVolume(unsigned newVal)
{
if (os_handle <= 0) //CAnnot set volume in loop back mode.
return FALSE;
int rc, deviceVol = (newVal << 8) | newVal;
if (direction == Player)
rc = ::ioctl(os_handle, MIXER_WRITE(SOUND_MIXER_VOLUME), &deviceVol);
else
rc = ::ioctl(os_handle, MIXER_WRITE(SOUND_MIXER_MIC), &deviceVol);
if (rc < 0) {
PTRACE(1, "PSoundChannelOSS::SetVolume failed : " << ::strerror(errno));
return FALSE;
}
return TRUE;
}
BOOL PSoundChannelOSS::GetVolume(unsigned &devVol)
{
if (os_handle <= 0) //CAnnot get volume in loop back mode.
return FALSE;
int vol, rc;
if (direction == Player)
rc = ::ioctl(os_handle, MIXER_READ(SOUND_MIXER_VOLUME), &vol);
else
rc = ::ioctl(os_handle, MIXER_READ(SOUND_MIXER_MIC), &vol);
if (rc < 0) {
PTRACE(1, "PSoundChannelOSS::GetVolume failed : " << ::strerror(errno)) ;
return FALSE;
}
devVol = vol & 0xff;
return TRUE;
}
// End of file
syntax highlighted by Code2HTML, v. 0.9.1