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.
arts/artsc/artscbackend.cpp

806 lines
18 KiB

/*
Copyright (C) 2000 Stefan Westerfeld
stefan@space.twc.de
2001 Matthias Kretz
kretz@kde.org
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
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#include "artsc.h"
#include "soundserver.h"
#include "stdsynthmodule.h"
#include <iostream>
#include <algorithm>
#include <queue>
#include <stdio.h>
#include <cstring>
#include <unistd.h>
#include <fcntl.h>
#include <math.h>
#include <assert.h>
#include "arts_export.h"
#define arts_backend_debug(x) ;
using namespace std;
using namespace Arts;
/**
* Base class for streams
*/
class Stream
{
protected:
SoundServer server;
float serverBufferTime;
bool _finished, isAttached;
int _samplingRate, _bits, _channels, pos;
string _name;
queue< DataPacket<mcopbyte>* > streamqueue;
int packetCount, packetCapacity;
int blockingIO;
/**
* returns the amount of bytes that will be played in a given amount of
* time in milliseconds
*/
int timeToBytes(float time)
{
float playSpeed = _channels * _samplingRate * _bits / 8;
return (int)(playSpeed * (time / 1000.0));
}
/**
* returns the time in milliseconds it takes with the current parameters
* to play a given amount of bytes
*/
float bytesToTime(int size)
{
float playSpeed = _channels * _samplingRate * _bits / 8;
return (1000.0 * ((float)size) / playSpeed);
}
int bufferSize() {
return packetCount * packetCapacity;
}
float bufferTime() {
return bytesToTime(bufferSize());
}
int bufferSpace() {
int space = 0;
attach();
/* make sure that our information is up-to-date */
Dispatcher::the()->ioManager()->processOneEvent(false);
if(!streamqueue.empty())
{
space += packetCapacity - pos; /* the first, half filled packet */
if(streamqueue.size() > 1) /* and the other, empty packets */
space += (streamqueue.size()-1)*packetCapacity;
}
return space;
}
int setBufferSize(int size)
{
/* don't change sizes when already streaming */
if(isAttached)
return ARTS_E_NOIMPL;
/*
* these parameters are usually a bad idea ;-) however we have to start
* somewhere, and maybe in two years, with a highly optimized kernel
* this is possible - for now, don't request the impossible or don't
* complain if it doesn't work
*/
packetCount = 3;
packetCapacity = 128;
/*
* - do not configure stream buffers smaller than the server
* recommended value
* - try to get more or less close to the value the application
* wants
*/
int needSize = max(size, timeToBytes(server.minStreamBufferTime()));
while(bufferSize() < needSize)
{
packetCount++;
if(packetCount == 8)
{
packetCount /= 2;
packetCapacity *= 2;
}
}
return bufferSize();
}
int packetSettings()
{
int settings = 0;
int cap = packetCapacity;
while(cap > 1)
{
settings++;
cap /= 2;
}
settings |= packetCount << 16;
return settings;
}
int setPacketSettings(int settings)
{
/* don't change sizes when already streaming */
if(isAttached)
return ARTS_E_NOIMPL;
packetCount = settings >> 16;
packetCapacity = 1;
int c = settings & 0xffff;
while(c > 0) {
packetCapacity *= 2;
c--;
}
/*
* - do not configure stream buffers smaller than the server
* recommended value
* - keep the packetSize the applications specified
*/
int needSize = timeToBytes(server.minStreamBufferTime());
while(bufferSize() < needSize)
packetCount++;
return packetSettings();
}
/**
* the stream has to attach itself
*/
virtual void attach() = 0;
public:
Stream(SoundServer server, int rate, int bits, int channels,
string name) : server(server), _finished(false), isAttached(false),
_samplingRate(rate), _bits(bits), _channels(channels), pos(0),
_name(name)
{
serverBufferTime = server.serverBufferTime();
stream_set(ARTS_P_BUFFER_SIZE,64*1024);
stream_set(ARTS_P_BLOCKING,1);
}
virtual ~Stream()
{
//
}
virtual int stream_set(arts_parameter_t param, int value)
{
int result;
switch(param) {
case ARTS_P_BUFFER_SIZE:
return setBufferSize(value);
case ARTS_P_BUFFER_TIME:
result = setBufferSize(timeToBytes(value));
if(result < 0) return result;
return (int)bufferTime();
case ARTS_P_PACKET_SETTINGS:
return setPacketSettings(value);
case ARTS_P_BLOCKING:
if(value != 0 && value != 1) return ARTS_E_NOIMPL;
blockingIO = value;
return blockingIO;
/*
* maybe ARTS_P_TOTAL_LATENCY _could_ be made writeable, the
* others are of course useless
*/
case ARTS_P_BUFFER_SPACE:
case ARTS_P_SERVER_LATENCY:
case ARTS_P_TOTAL_LATENCY:
case ARTS_P_PACKET_SIZE:
case ARTS_P_PACKET_COUNT:
return ARTS_E_NOIMPL;
}
return ARTS_E_NOIMPL;
}
virtual int stream_get(arts_parameter_t param)
{
switch(param) {
case ARTS_P_BUFFER_SIZE:
return bufferSize();
case ARTS_P_BUFFER_TIME:
return (int)bufferTime();
case ARTS_P_BUFFER_SPACE:
return bufferSpace();
case ARTS_P_PACKET_SETTINGS:
return packetSettings();
case ARTS_P_SERVER_LATENCY:
return (int)serverBufferTime;
case ARTS_P_TOTAL_LATENCY:
return stream_get(ARTS_P_SERVER_LATENCY)
+ stream_get(ARTS_P_BUFFER_TIME);
case ARTS_P_BLOCKING:
return blockingIO;
case ARTS_P_PACKET_SIZE:
return packetCapacity;
case ARTS_P_PACKET_COUNT:
return packetCount;
}
return ARTS_E_NOIMPL;
}
virtual int write(const mcopbyte * /*data*/, int /*size*/)
{
return ARTS_E_NOIMPL;
}
virtual int read(mcopbyte * /*data*/, int /*size*/)
{
return ARTS_E_NOIMPL;
}
virtual void close() = 0;
int suspend()
{
if(isAttached)
{
return server.suspend();
}
return 0;
}
int suspended()
{
if(isAttached)
{
return 0;
}
return server.suspended();
}
};
class Receiver : public ByteSoundReceiver_skel,
public StdSynthModule,
virtual public Stream
{
/*
* FIXME: bsWrapper is a more or less ugly trick to be able to use
* this object although not using smartwrappers to access it
*/
ByteSoundReceiver bsWrapper;
protected:
virtual void attach()
{
if(!isAttached)
{
isAttached = true;
server.attachRecorder(bsWrapper);
start();
/*
* TODO: this processOneEvent looks a bit strange here... it is
* there since StdIOManager does block 5 seconds on the first
* arts_write if it isn't - although notifications are pending
*
* Probably the real solution is to rewrite the
* StdIOManager::processOneEvent function. (And maybe drop the
* assumption that aRts will not block when an infinite amount
* of notifications is pending - I mean: will it ever happen?)
*/
Dispatcher::the()->ioManager()->processOneEvent(false);
}
}
public:
Receiver(SoundServer server, int rate, int bits, int channels,
string name) : Stream( server, rate, bits, channels, name)
{
bsWrapper = ByteSoundReceiver::_from_base(this);
}
virtual ~Receiver() {
//
}
long samplingRate() { return _samplingRate; }
long channels() { return _channels; }
long bits() { return _bits; }
bool finished() { return _finished; }
string title() { return _name; }
void process_indata(DataPacket<mcopbyte> *packet)
{
streamqueue.push(packet);
}
void close()
{
if(isAttached)
{
/* remove all packets from the streamqueue */
while(!streamqueue.empty())
{
DataPacket<mcopbyte> *packet = streamqueue.front();
packet->processed();
streamqueue.pop();
}
server.detachRecorder(bsWrapper);
}
// similar effect like "delete this;"
bsWrapper = ByteSoundReceiver::null();
}
int read(mcopbyte *data, int size)
{
attach();
int remaining = size;
while(remaining)
{
if(blockingIO)
{
/* C API blocking style read */
while(streamqueue.empty())
Dispatcher::the()->ioManager()->processOneEvent(true);
}
else
{
/* non blocking I/O */
if(streamqueue.empty())
Dispatcher::the()->ioManager()->processOneEvent(false);
/* still no more packets to read? */
if(streamqueue.empty())
return size - remaining;
}
/* get a packet */
DataPacket<mcopbyte> *packet = streamqueue.front();
/* copy some data from there */
int tocopy = min(remaining,packet->size-pos);
memcpy(data,&packet->contents[pos],tocopy);
pos += tocopy;
data += tocopy;
remaining -= tocopy;
/* have we read the whole packet? then get rid of it */
if(pos == packet->size)
{
packet->processed();
streamqueue.pop();
pos = 0;
}
}
/* no possible error conditions */
return size;
}
};
class Sender : public ByteSoundProducerV2_skel,
public StdSynthModule,
virtual public Stream
{
/*
* FIXME: bsWrapper is a more or less ugly trick to be able to use
* this object although not using smartwrappers to access it
*/
ByteSoundProducerV2 bsWrapper;
protected:
virtual void attach()
{
if(!isAttached)
{
isAttached = true;
server.attach(bsWrapper);
start();
/*
* TODO: this processOneEvent looks a bit strange here... it is
* there since StdIOManager does block 5 seconds on the first
* arts_write if it isn't - although notifications are pending
*
* Probably the real solution is to rewrite the
* StdIOManager::processOneEvent function. (And maybe drop the
* assumption that aRts will not block when an infinite amount
* of notifications is pending - I mean: will it ever happen?)
*/
Dispatcher::the()->ioManager()->processOneEvent(false);
}
}
public:
Sender(SoundServer server, int rate, int bits, int channels,
string name) : Stream( server, rate, bits, channels, name)
{
bsWrapper = ByteSoundProducerV2::_from_base(this);
}
virtual ~Sender() {
//
}
long samplingRate() { return _samplingRate; }
long channels() { return _channels; }
long bits() { return _bits; }
bool finished() { return _finished; }
string title() { return _name; }
void streamStart()
{
/*
* start streaming
*/
outdata.setPull(packetCount, packetCapacity);
}
void request_outdata(DataPacket<mcopbyte> *packet)
{
streamqueue.push(packet);
}
void close()
{
if(isAttached)
{
if(pos != 0)
{
/* send the last half-filled packet */
DataPacket<mcopbyte> *packet = streamqueue.front();
packet->size = pos;
packet->send();
streamqueue.pop();
}
outdata.endPull();
/* remove all packets from the streamqueue */
while(!streamqueue.empty())
{
DataPacket<mcopbyte> *packet = streamqueue.front();
packet->size = 0;
packet->send();
streamqueue.pop();
}
server.detach(bsWrapper);
}
// similar effect like "delete this;"
Arts::ByteSoundProducerV2_base* x = _copy();
bsWrapper = ByteSoundProducerV2::null();
x->_release();
}
int write(const mcopbyte *data, int size)
{
attach();
int remaining = size;
while(remaining)
{
if(blockingIO)
{
/* C API blocking style write */
/* we're not waiting for any data here, but rather for
* DataPackets that we can fill */
while(streamqueue.empty())
Dispatcher::the()->ioManager()->processOneEvent(true);
}
else
{
/* non blocking I/O */
if(streamqueue.empty())
Dispatcher::the()->ioManager()->processOneEvent(false);
/* still no more space to write? */
if(streamqueue.empty())
return size - remaining;
}
/* get a packet */
DataPacket<mcopbyte> *packet = streamqueue.front();
/* copy some data there */
int tocopy = min(remaining,packetCapacity-pos);
memcpy(&packet->contents[pos],data,tocopy);
pos += tocopy;
data += tocopy;
remaining -= tocopy;
/* have we filled up the packet? then send it */
if(pos == packetCapacity)
{
packet->size = packetCapacity;
packet->send();
streamqueue.pop();
pos = 0;
}
}
/* no possible error conditions */
return size;
}
};
class ArtsCApi {
protected:
static ArtsCApi *instance;
int refcnt;
Dispatcher dispatcher;
SoundServer server;
ArtsCApi() : refcnt(1), server(Reference("global:Arts_SoundServer"))
{
//
}
public:
// C Api commands
int init() {
if(server.isNull())
return ARTS_E_NOSERVER;
return 0;
}
int suspend() {
if(!server.isNull())
return server.suspend()? 1:0;
return ARTS_E_NOSERVER;
}
int suspended() {
if(!server.isNull())
return server.suspended()? 1:0;
return ARTS_E_NOSERVER;
}
void free() {
// nothing to do
}
arts_stream_t play_stream(int rate, int bits, int channels, const char *name)
{
if(server.isNull())
return 0;
return (arts_stream_t)static_cast<Stream *>(new Sender(server,rate,bits,channels,name));
}
arts_stream_t record_stream(int rate, int bits, int channels, const char *name)
{
if(server.isNull())
return 0;
return (arts_stream_t)static_cast<Stream *>(new Receiver(server,rate,bits,channels,name));
}
void close_stream(arts_stream_t stream)
{
if(server.isNull())
return;
if(!stream)
return;
static_cast<Stream *>(stream)->close();
}
int write(arts_stream_t stream, const void *data, int size)
{
if(server.isNull())
return ARTS_E_NOSERVER;
if(!stream)
return ARTS_E_NOSTREAM;
return static_cast<Stream *>(stream)->write((const mcopbyte *)data,size);
}
int read(arts_stream_t stream, void *data, int size)
{
if(server.isNull())
return ARTS_E_NOSERVER;
if(!stream)
return ARTS_E_NOSTREAM;
return static_cast<Stream *>(stream)->read((mcopbyte *)data,size);
}
int stream_set(arts_stream_t stream, arts_parameter_t param, int value)
{
if(server.isNull())
return ARTS_E_NOSERVER;
if(!stream)
return ARTS_E_NOSTREAM;
return static_cast<Stream *>(stream)->stream_set(param,value);
}
int stream_get(arts_stream_t stream, arts_parameter_t param)
{
if(server.isNull())
return ARTS_E_NOSERVER;
if(!stream)
return ARTS_E_NOSTREAM;
return static_cast<Stream *>(stream)->stream_get(param);
}
// allocation and freeing of the class
static ArtsCApi *the() {
return instance;
}
static void ref() {
if(!instance)
instance = new ArtsCApi();
else
instance->refcnt++;
}
static void release() {
assert(instance);
assert(instance->refcnt > 0);
instance->refcnt--;
if(instance->refcnt == 0)
{
delete instance;
instance = 0;
}
}
};
//----------------------------- static members -------------------------------
ArtsCApi *ArtsCApi::instance = 0;
//------------------ wrappers from C to C++ class ----------------------------
extern "C" ARTSC_EXPORT int arts_backend_init()
{
arts_backend_debug("arts_backend_init");
ArtsCApi::ref();
// if init fails, don't expect free, and don't expect that the user
// continues using other API functions
int rc = ArtsCApi::the()->init();
if(rc < 0) ArtsCApi::release();
return rc;
}
extern "C" ARTSC_EXPORT int arts_backend_suspend()
{
if(!ArtsCApi::the()) return ARTS_E_NOINIT;
arts_backend_debug("arts_backend_suspend");
return ArtsCApi::the()->suspend();
}
extern "C" ARTSC_EXPORT int arts_backend_suspended()
{
if(!ArtsCApi::the()) return ARTS_E_NOINIT;
arts_backend_debug("arts_backend_suspended");
return ArtsCApi::the()->suspended();
}
extern "C" ARTSC_EXPORT void arts_backend_free()
{
if(!ArtsCApi::the()) return;
arts_backend_debug("arts_backend_free");
ArtsCApi::the()->free();
ArtsCApi::release();
}
extern "C" ARTSC_EXPORT arts_stream_t arts_backend_play_stream(int rate, int bits, int channels, const char *name)
{
if(!ArtsCApi::the()) return 0;
arts_backend_debug("arts_backend_play_stream");
return ArtsCApi::the()->play_stream(rate,bits,channels,name);
}
extern "C" ARTSC_EXPORT arts_stream_t arts_backend_record_stream(int rate, int bits, int channels, const char *name)
{
if(!ArtsCApi::the()) return 0;
arts_backend_debug("arts_backend_record_stream");
return ArtsCApi::the()->record_stream(rate,bits,channels,name);
}
extern "C" ARTSC_EXPORT void arts_backend_close_stream(arts_stream_t stream)
{
if(!ArtsCApi::the()) return;
arts_backend_debug("arts_backend_close_stream");
ArtsCApi::the()->close_stream(stream);
}
extern "C" ARTSC_EXPORT int arts_backend_read(arts_stream_t stream, void *buffer, int count)
{
if(!ArtsCApi::the()) return ARTS_E_NOINIT;
arts_backend_debug("arts_backend_read");
return ArtsCApi::the()->read(stream,buffer,count);
}
extern "C" ARTSC_EXPORT int arts_backend_write(arts_stream_t stream, const void *buffer,
int count)
{
if(!ArtsCApi::the()) return ARTS_E_NOINIT;
arts_backend_debug("arts_backend_write");
return ArtsCApi::the()->write(stream,buffer,count);
}
extern "C" ARTSC_EXPORT int arts_backend_stream_set(arts_stream_t stream,
arts_parameter_t param, int value)
{
if(!ArtsCApi::the()) return ARTS_E_NOINIT;
arts_backend_debug("arts_stream_set");
return ArtsCApi::the()->stream_set(stream,param,value);
}
extern "C" ARTSC_EXPORT int arts_backend_stream_get(arts_stream_t stream,
arts_parameter_t param)
{
if(!ArtsCApi::the()) return ARTS_E_NOINIT;
arts_backend_debug("arts_stream_get");
return ArtsCApi::the()->stream_get(stream,param);
}