You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
646 lines
13 KiB
646 lines
13 KiB
15 years ago
|
/*
|
||
|
|
||
|
Copyright (C) 2000 Stefan Westerfeld
|
||
|
stefan@space.twc.de
|
||
|
|
||
|
This library is free software; you can redistribute it and/or
|
||
|
modify it under the terms of the GNU Library General Public
|
||
|
License as published by the Free Software Foundation; either
|
||
|
version 2 of the License, or (at your option) any later version.
|
||
|
|
||
|
This library is distributed in the hope that it will be useful,
|
||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||
|
Library General Public License for more details.
|
||
|
|
||
|
You should have received a copy of the GNU Library General Public License
|
||
|
along with this library; see the file COPYING.LIB. If not, write to
|
||
15 years ago
|
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
|
||
|
Boston, MA 02110-1301, USA.
|
||
15 years ago
|
|
||
|
*/
|
||
|
|
||
|
#ifdef HAVE_CONFIG_H
|
||
|
#include <config.h>
|
||
|
#endif
|
||
|
|
||
|
#include <sys/types.h>
|
||
|
#include <sys/ioctl.h>
|
||
|
#include <sys/time.h>
|
||
|
#include <sys/stat.h>
|
||
|
|
||
|
#ifdef HAVE_SYS_SELECT_H
|
||
|
#include <sys/select.h> // Needed on some systems.
|
||
|
#endif
|
||
|
|
||
|
#ifdef HAVE_SYS_SOUNDCARD_H
|
||
|
#include <sys/soundcard.h>
|
||
|
#endif
|
||
|
|
||
|
#include <assert.h>
|
||
|
#include <errno.h>
|
||
|
#include <fcntl.h>
|
||
|
#include <stdio.h>
|
||
|
#include <stdlib.h>
|
||
|
#include <unistd.h>
|
||
|
#include <iostream>
|
||
|
#include <algorithm>
|
||
|
#include <cstring>
|
||
|
|
||
|
#include "debug.h"
|
||
|
#include "audiosubsys.h"
|
||
|
#include "audioio.h"
|
||
|
|
||
|
#define DEFAULT_DEVICE_NAME "/dev/dsp"
|
||
|
|
||
|
#undef DEBUG_WAVEFORM
|
||
|
#ifdef DEBUG_WAVEFORM
|
||
|
#include <fstream>
|
||
|
#endif
|
||
|
|
||
|
using namespace std;
|
||
|
using namespace Arts;
|
||
|
|
||
|
//--- automatic startup class
|
||
|
|
||
|
static AudioSubSystemStart aStart;
|
||
|
|
||
|
void AudioSubSystemStart::startup()
|
||
|
{
|
||
|
_instance = new AudioSubSystem();
|
||
|
}
|
||
|
|
||
|
void AudioSubSystemStart::shutdown()
|
||
|
{
|
||
|
delete _instance;
|
||
|
}
|
||
|
|
||
|
//--- AudioSubSystemPrivate data
|
||
|
|
||
|
class Arts::AudioSubSystemPrivate
|
||
|
{
|
||
|
public:
|
||
|
#ifdef DEBUG_WAVEFORM
|
||
|
ofstream plotfile;
|
||
|
#endif
|
||
|
AudioIO *audioIO;
|
||
|
string audioIOName;
|
||
|
bool audioIOInit;
|
||
|
|
||
|
unsigned int adjustDuplexOffsetIndex;
|
||
|
int adjustDuplexOffset[4];
|
||
|
int adjustDuplexCount;
|
||
|
};
|
||
|
|
||
|
//--- AudioSubSystem implementation
|
||
|
|
||
|
AudioSubSystem *AudioSubSystem::the()
|
||
|
{
|
||
|
return aStart.the();
|
||
|
}
|
||
|
|
||
|
const char *AudioSubSystem::error()
|
||
|
{
|
||
|
return _error.c_str();
|
||
|
}
|
||
|
|
||
|
AudioSubSystem::AudioSubSystem()
|
||
|
{
|
||
|
d = new AudioSubSystemPrivate;
|
||
|
#ifdef DEBUG_WAVEFORM
|
||
|
d->plotfile.open( "/dev/shm/audiosubsystem.plot" );
|
||
|
#endif
|
||
|
d->audioIO = 0;
|
||
|
d->audioIOInit = false;
|
||
|
|
||
|
_running = false;
|
||
|
consumer = 0;
|
||
|
producer = 0;
|
||
|
fragment_buffer = 0;
|
||
|
}
|
||
|
|
||
|
AudioSubSystem::~AudioSubSystem()
|
||
|
{
|
||
|
delete d->audioIO;
|
||
|
delete d;
|
||
|
}
|
||
|
|
||
|
bool AudioSubSystem::attachProducer(ASProducer *producer)
|
||
|
{
|
||
|
assert(producer);
|
||
|
if(this->producer) return false;
|
||
|
|
||
|
this->producer = producer;
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
bool AudioSubSystem::attachConsumer(ASConsumer *consumer)
|
||
|
{
|
||
|
assert(consumer);
|
||
|
if(this->consumer) return false;
|
||
|
|
||
|
this->consumer = consumer;
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
void AudioSubSystem::detachProducer()
|
||
|
{
|
||
|
assert(producer);
|
||
|
producer = 0;
|
||
|
|
||
|
if(_running) close();
|
||
|
}
|
||
|
|
||
|
void AudioSubSystem::detachConsumer()
|
||
|
{
|
||
|
assert(consumer);
|
||
|
consumer = 0;
|
||
|
|
||
|
if(_running) close();
|
||
|
}
|
||
|
|
||
|
/* initially creates default AudioIO */
|
||
|
void AudioSubSystem::initAudioIO()
|
||
|
{
|
||
|
/* auto detect */
|
||
|
if(!d->audioIOInit)
|
||
|
{
|
||
|
string bestName;
|
||
|
int bestValue = 0;
|
||
|
|
||
|
arts_debug("autodetecting driver: ");
|
||
|
for(int i = 0; i < AudioIO::queryAudioIOCount(); i++)
|
||
|
{
|
||
|
string name = AudioIO::queryAudioIOParamStr(i, AudioIO::name);
|
||
|
AudioIO *aio = AudioIO::createAudioIO(name.c_str());
|
||
|
int value = aio->getParam(AudioIO::autoDetect);
|
||
|
|
||
|
arts_debug(" - %s: %d", name.c_str(), value);
|
||
|
if(value > bestValue)
|
||
|
{
|
||
|
bestName = name;
|
||
|
bestValue = value;
|
||
|
}
|
||
|
delete aio;
|
||
|
}
|
||
|
if(bestValue)
|
||
|
{
|
||
|
arts_debug("... which means we'll default to %s", bestName.c_str());
|
||
|
audioIO(bestName);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
arts_debug("... nothing we could use as default found");
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void AudioSubSystem::audioIO(const string& audioIO)
|
||
|
{
|
||
|
if(d->audioIO)
|
||
|
delete d->audioIO;
|
||
|
|
||
|
d->audioIOName = audioIO;
|
||
|
d->audioIO = AudioIO::createAudioIO(audioIO.c_str());
|
||
|
d->audioIOInit = true;
|
||
|
}
|
||
|
|
||
|
string AudioSubSystem::audioIO()
|
||
|
{
|
||
|
initAudioIO();
|
||
|
|
||
|
return d->audioIOName;
|
||
|
}
|
||
|
|
||
|
void AudioSubSystem::deviceName(const string& deviceName)
|
||
|
{
|
||
|
initAudioIO();
|
||
|
if(!d->audioIO) return;
|
||
|
|
||
|
d->audioIO->setParamStr(AudioIO::deviceName, deviceName.c_str());
|
||
|
}
|
||
|
|
||
|
string AudioSubSystem::deviceName()
|
||
|
{
|
||
|
initAudioIO();
|
||
|
if(!d->audioIO) return "";
|
||
|
|
||
|
return d->audioIO->getParamStr(AudioIO::deviceName);
|
||
|
}
|
||
|
|
||
|
void AudioSubSystem::fragmentCount(int fragmentCount)
|
||
|
{
|
||
|
initAudioIO();
|
||
|
if(!d->audioIO) return;
|
||
|
|
||
|
d->audioIO->setParam(AudioIO::fragmentCount, fragmentCount);
|
||
|
}
|
||
|
|
||
|
int AudioSubSystem::fragmentCount()
|
||
|
{
|
||
|
initAudioIO();
|
||
|
if(!d->audioIO) return 0;
|
||
|
|
||
|
return d->audioIO->getParam(AudioIO::fragmentCount);
|
||
|
}
|
||
|
|
||
|
void AudioSubSystem::fragmentSize(int fragmentSize)
|
||
|
{
|
||
|
initAudioIO();
|
||
|
if(!d->audioIO) return;
|
||
|
|
||
|
d->audioIO->setParam(AudioIO::fragmentSize, fragmentSize);
|
||
|
}
|
||
|
|
||
|
int AudioSubSystem::fragmentSize()
|
||
|
{
|
||
|
initAudioIO();
|
||
|
if(!d->audioIO) return 0;
|
||
|
|
||
|
return d->audioIO->getParam(AudioIO::fragmentSize);
|
||
|
}
|
||
|
|
||
|
void AudioSubSystem::samplingRate(int samplingRate)
|
||
|
{
|
||
|
initAudioIO();
|
||
|
if(!d->audioIO) return;
|
||
|
|
||
|
d->audioIO->setParam(AudioIO::samplingRate, samplingRate);
|
||
|
}
|
||
|
|
||
|
int AudioSubSystem::samplingRate()
|
||
|
{
|
||
|
initAudioIO();
|
||
|
if(!d->audioIO) return 0;
|
||
|
|
||
|
return d->audioIO->getParam(AudioIO::samplingRate);
|
||
|
}
|
||
|
|
||
|
void AudioSubSystem::channels(int channels)
|
||
|
{
|
||
|
initAudioIO();
|
||
|
if(!d->audioIO) return;
|
||
|
|
||
|
d->audioIO->setParam(AudioIO::channels, channels);
|
||
|
}
|
||
|
|
||
|
int AudioSubSystem::channels()
|
||
|
{
|
||
|
initAudioIO();
|
||
|
if(!d->audioIO) return 0;
|
||
|
|
||
|
return d->audioIO->getParam(AudioIO::channels);
|
||
|
}
|
||
|
|
||
|
void AudioSubSystem::format(int format)
|
||
|
{
|
||
|
initAudioIO();
|
||
|
if(!d->audioIO) return;
|
||
|
|
||
|
d->audioIO->setParam(AudioIO::format, format);
|
||
|
}
|
||
|
|
||
|
int AudioSubSystem::format()
|
||
|
{
|
||
|
initAudioIO();
|
||
|
if(!d->audioIO) return 0;
|
||
|
|
||
|
return d->audioIO->getParam(AudioIO::format);
|
||
|
}
|
||
|
|
||
|
int AudioSubSystem::bits()
|
||
|
{
|
||
|
int _format = format();
|
||
|
arts_assert(_format == 0 || _format == 8 || _format == 16 || _format == 17 || _format == 32);
|
||
|
return (_format & (32 | 16 | 8));
|
||
|
}
|
||
|
|
||
|
void AudioSubSystem::fullDuplex(bool fullDuplex)
|
||
|
{
|
||
|
initAudioIO();
|
||
|
if(!d->audioIO) return;
|
||
|
|
||
|
int direction = fullDuplex?3:2;
|
||
|
d->audioIO->setParam(AudioIO::direction, direction);
|
||
|
}
|
||
|
|
||
|
bool AudioSubSystem::fullDuplex()
|
||
|
{
|
||
|
initAudioIO();
|
||
|
if(!d->audioIO) return false;
|
||
|
|
||
|
return d->audioIO->getParam(AudioIO::direction) == 3;
|
||
|
}
|
||
|
|
||
|
int AudioSubSystem::selectReadFD()
|
||
|
{
|
||
|
initAudioIO();
|
||
|
if(!d->audioIO) return false;
|
||
|
|
||
|
return d->audioIO->getParam(AudioIO::selectReadFD);
|
||
|
}
|
||
|
|
||
|
int AudioSubSystem::selectWriteFD()
|
||
|
{
|
||
|
initAudioIO();
|
||
|
if(!d->audioIO) return false;
|
||
|
|
||
|
return d->audioIO->getParam(AudioIO::selectWriteFD);
|
||
|
}
|
||
|
|
||
|
bool AudioSubSystem::check()
|
||
|
{
|
||
|
bool ok = open();
|
||
|
|
||
|
if(ok) close();
|
||
|
return ok;
|
||
|
}
|
||
|
|
||
|
bool AudioSubSystem::open()
|
||
|
{
|
||
|
assert(!_running);
|
||
|
|
||
|
initAudioIO();
|
||
|
if(!d->audioIO)
|
||
|
{
|
||
|
if(d->audioIOName.empty())
|
||
|
_error = "couldn't auto detect which audio I/O method to use";
|
||
|
else
|
||
|
_error = "unable to select '"+d->audioIOName+"' style audio I/O";
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if(d->audioIO->open())
|
||
|
{
|
||
|
_running = true;
|
||
|
|
||
|
_fragmentSize = d->audioIO->getParam(AudioIO::fragmentSize);
|
||
|
_fragmentCount = d->audioIO->getParam(AudioIO::fragmentCount);
|
||
|
|
||
|
// allocate global buffer to do I/O
|
||
|
assert(fragment_buffer == 0);
|
||
|
fragment_buffer = new char[_fragmentSize];
|
||
|
|
||
|
d->adjustDuplexCount = 0;
|
||
|
return true;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
_error = d->audioIO->getParamStr(AudioIO::lastError);
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void AudioSubSystem::close()
|
||
|
{
|
||
|
assert(_running);
|
||
|
assert(d->audioIO);
|
||
|
|
||
|
d->audioIO->close();
|
||
|
|
||
|
wBuffer.clear();
|
||
|
rBuffer.clear();
|
||
|
|
||
|
_running = false;
|
||
|
if(fragment_buffer)
|
||
|
{
|
||
|
delete[] fragment_buffer;
|
||
|
fragment_buffer = 0;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
bool AudioSubSystem::running()
|
||
|
{
|
||
|
return _running;
|
||
|
}
|
||
|
|
||
|
void AudioSubSystem::handleIO(int type)
|
||
|
{
|
||
|
assert(d->audioIO);
|
||
|
|
||
|
if(type & ioRead)
|
||
|
{
|
||
|
int len = d->audioIO->read(fragment_buffer,_fragmentSize);
|
||
|
|
||
|
if(len > 0)
|
||
|
{
|
||
|
if(rBuffer.size() < _fragmentSize * _fragmentCount * bits() / 8 * channels())
|
||
|
{
|
||
|
rBuffer.write(len,fragment_buffer);
|
||
|
#ifdef DEBUG_WAVEFORM
|
||
|
float * end = (float *)(fragment_buffer + len);
|
||
|
float * floatbuffer = (float *)fragment_buffer;
|
||
|
while(floatbuffer < end)
|
||
|
{
|
||
|
d->plotfile << *floatbuffer++ << "\n";
|
||
|
++floatbuffer;
|
||
|
}
|
||
|
#endif
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
arts_debug( "AudioSubSystem: rBuffer is too full" );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if(type & ioWrite)
|
||
|
{
|
||
|
/*
|
||
|
* make sure that we have a fragment full of data at least
|
||
|
*/
|
||
|
Rewrite:
|
||
|
while(wBuffer.size() < _fragmentSize)
|
||
|
{
|
||
|
long wbsz = wBuffer.size();
|
||
|
producer->needMore();
|
||
|
|
||
|
if(wbsz == wBuffer.size())
|
||
|
{
|
||
|
/*
|
||
|
* Even though we asked the client to supply more
|
||
|
* data, he didn't give us more. So we can't supply
|
||
|
* output data as well. Bad luck. Might produce a
|
||
|
* buffer underrun - but we can't help here.
|
||
|
*/
|
||
|
arts_info("full duplex: no more data available (underrun)");
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* look how much we really can write without blocking
|
||
|
*/
|
||
|
int space = d->audioIO->getParam(AudioIO::canWrite);
|
||
|
int can_write = min(space, _fragmentSize);
|
||
|
|
||
|
if(can_write > 0)
|
||
|
{
|
||
|
/*
|
||
|
* ok, so write it (as we checked that our buffer has enough data
|
||
|
* to do so and the soundcardbuffer has enough data to handle this
|
||
|
* write, nothing can go wrong here)
|
||
|
*/
|
||
|
int rSize = wBuffer.read(can_write,fragment_buffer);
|
||
|
assert(rSize == can_write);
|
||
|
|
||
|
int len = d->audioIO->write(fragment_buffer,can_write);
|
||
|
if(len != can_write)
|
||
|
arts_fatal("AudioSubSystem::handleIO: write failed\n"
|
||
|
"len = %d, can_write = %d, errno = %d (%s)\n\n"
|
||
|
"This might be a sound hardware/driver specific problem"
|
||
|
" (see aRts FAQ)",len,can_write,errno,strerror(errno));
|
||
|
|
||
|
if(fullDuplex())
|
||
|
{
|
||
|
/*
|
||
|
* if we're running full duplex, here is a good place to check
|
||
|
* for full duplex drift
|
||
|
*/
|
||
|
d->adjustDuplexCount += can_write;
|
||
|
if(d->adjustDuplexCount > samplingRate())
|
||
|
{
|
||
|
adjustDuplexBuffers();
|
||
|
d->adjustDuplexCount = 0;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// If we can write a fragment more, then do so right now:
|
||
|
if (space >= _fragmentSize*2) goto Rewrite;
|
||
|
}
|
||
|
|
||
|
assert((type & ioExcept) == 0);
|
||
|
}
|
||
|
|
||
|
void AudioSubSystem::read(void *buffer, int size)
|
||
|
{
|
||
|
/* if not enough data can be read, produce some */
|
||
|
while(rBuffer.size() < size)
|
||
|
adjustInputBuffer(1);
|
||
|
|
||
|
/* finally, just take the data out of the input buffer */
|
||
|
int rSize = rBuffer.read(size,buffer);
|
||
|
assert(rSize == size);
|
||
|
}
|
||
|
|
||
|
void AudioSubSystem::write(void *buffer, int size)
|
||
|
{
|
||
|
wBuffer.write(size,buffer);
|
||
|
}
|
||
|
|
||
|
float AudioSubSystem::outputDelay()
|
||
|
{
|
||
|
int fsize = _fragmentSize;
|
||
|
int fcount = _fragmentCount;
|
||
|
|
||
|
if(fsize > 0 && fcount > 0) // not all AudioIO classes need to support this
|
||
|
{
|
||
|
double hardwareBuffer = fsize * fcount;
|
||
|
double freeOutputSpace = d->audioIO->getParam(AudioIO::canWrite);
|
||
|
double playSpeed = channels() * samplingRate() * (bits() / 8);
|
||
|
|
||
|
return (hardwareBuffer - freeOutputSpace) / playSpeed;
|
||
|
}
|
||
|
else return 0.0;
|
||
|
}
|
||
|
|
||
|
void AudioSubSystem::adjustDuplexBuffers()
|
||
|
{
|
||
|
int fsize = _fragmentSize;
|
||
|
int fcount = _fragmentCount;
|
||
|
|
||
|
if(fsize > 0 && fcount > 0) // not all AudioIO classes need to support this
|
||
|
{
|
||
|
int bound = 2; //max(fcount/2, 1);
|
||
|
int optimalOffset = fsize * (fcount + bound);
|
||
|
int minOffset = fsize * fcount;
|
||
|
int maxOffset = fsize * (fcount + 2 * bound);
|
||
|
|
||
|
int canRead = d->audioIO->getParam(AudioIO::canRead);
|
||
|
if(canRead < 0)
|
||
|
{
|
||
|
arts_warning("AudioSubSystem::adjustDuplexBuffers: canRead < 0?");
|
||
|
canRead = 0;
|
||
|
}
|
||
|
|
||
|
int canWrite = d->audioIO->getParam(AudioIO::canWrite);
|
||
|
if(canWrite < 0)
|
||
|
{
|
||
|
arts_warning("AudioSubSystem::adjustDuplexBuffers: canWrite < 0?");
|
||
|
canWrite = 0;
|
||
|
}
|
||
|
|
||
|
int currentOffset = rBuffer.size() + wBuffer.size()
|
||
|
+ canRead + max((fsize * fcount) - canWrite, 0);
|
||
|
|
||
|
d->adjustDuplexOffset[d->adjustDuplexOffsetIndex++ & 3] = currentOffset;
|
||
|
if(d->adjustDuplexOffsetIndex <= 4) return;
|
||
|
|
||
|
int avgOffset;
|
||
|
avgOffset = d->adjustDuplexOffset[0]
|
||
|
+ d->adjustDuplexOffset[1]
|
||
|
+ d->adjustDuplexOffset[2]
|
||
|
+ d->adjustDuplexOffset[3];
|
||
|
avgOffset /= 4;
|
||
|
|
||
|
/*
|
||
|
printf("offset: %d avg %d min %d opt %d max %d\r", currentOffset,
|
||
|
avgOffset, minOffset, optimalOffset, maxOffset);
|
||
|
fflush(stdout);
|
||
|
*/
|
||
|
if(minOffset <= avgOffset && avgOffset <= maxOffset)
|
||
|
return;
|
||
|
|
||
|
d->adjustDuplexOffsetIndex = 0;
|
||
|
int adjust = (optimalOffset - currentOffset) / _fragmentSize;
|
||
|
arts_debug("AudioSubSystem::adjustDuplexBuffers(%d)", adjust);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void AudioSubSystem::adjustInputBuffer(int count)
|
||
|
{
|
||
|
if(format() == 8)
|
||
|
{
|
||
|
memset( fragment_buffer, 0x80, _fragmentSize );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
memset( fragment_buffer, 0, _fragmentSize );
|
||
|
}
|
||
|
|
||
|
while(count > 0 && rBuffer.size() < _fragmentSize * _fragmentCount * 4)
|
||
|
{
|
||
|
rBuffer.write(_fragmentSize, fragment_buffer);
|
||
|
#ifdef DEBUG_WAVEFORM
|
||
|
float * end = (float *)(fragment_buffer + _fragmentSize);
|
||
|
float * floatbuffer = (float *)fragment_buffer;
|
||
|
while(floatbuffer < end)
|
||
|
{
|
||
|
d->plotfile << *floatbuffer++ << "\n";
|
||
|
++floatbuffer;
|
||
|
}
|
||
|
#endif
|
||
|
count--;
|
||
|
}
|
||
|
|
||
|
while(count < 0 && rBuffer.size() >= _fragmentSize)
|
||
|
{
|
||
|
rBuffer.read(_fragmentSize, fragment_buffer);
|
||
|
count++;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void AudioSubSystem::emergencyCleanup()
|
||
|
{
|
||
|
if(producer || consumer)
|
||
|
{
|
||
|
fprintf(stderr, "AudioSubSystem::emergencyCleanup\n");
|
||
|
|
||
|
if(producer)
|
||
|
detachProducer();
|
||
|
if(consumer)
|
||
|
detachConsumer();
|
||
|
}
|
||
|
}
|