src/gui/embedded/qsoundqss_qws.cpp
changeset 0 1918ee327afb
child 4 3b1da2848fc7
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/gui/embedded/qsoundqss_qws.cpp	Mon Jan 11 14:00:40 2010 +0000
@@ -0,0 +1,1530 @@
+/****************************************************************************
+**
+** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the QtGui module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the Technology Preview License Agreement accompanying
+** this package.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file.  Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights.  These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** If you have questions regarding the use of this file, please contact
+** Nokia at qt-info@nokia.com.
+**
+**
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qsoundqss_qws.h"
+
+#ifndef QT_NO_SOUND
+#include <qbytearray.h>
+#include <qlist.h>
+#include <qsocketnotifier.h>
+#include <qfile.h>
+#include <qfileinfo.h>
+#include <qstringlist.h>
+#include <qevent.h>
+#include <qalgorithms.h>
+#include <qtimer.h>
+#include <qpointer.h>
+#include <qendian.h>
+#include <private/qcore_unix_p.h> // overrides QT_OPEN
+
+#include <unistd.h>
+#include <stdlib.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <time.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/ioctl.h>
+#include <sys/soundcard.h>
+
+#include <qdebug.h>
+
+#include <qvfbhdr.h>
+
+extern int errno;
+
+QT_BEGIN_NAMESPACE
+
+#define QT_QWS_SOUND_16BIT 1 // or 0, or undefined for always 0
+#define QT_QWS_SOUND_STEREO 1 // or 0, or undefined for always 0
+
+// Zaurus SL5000D doesn't seem to return any error if setting to 44000 and it fails,
+// however 44100 works, 44100 is more common that 44000.
+static int sound_speed = 44100;
+#ifndef QT_NO_QWS_SOUNDSERVER
+extern int qws_display_id;
+#endif
+
+static char *zeroMem = 0;
+
+struct QRiffChunk {
+    char id[4];
+    quint32 size;
+    char data[4/*size*/];
+};
+
+#if defined(QT_QWS_IPAQ)
+static const int sound_fragment_size = 12;
+#else
+static const int sound_fragment_size = 12;
+#endif
+static const int sound_buffer_size = 1 << sound_fragment_size;
+// nb. there will be an sound startup delay of
+//        2^sound_fragment_size / sound_speed seconds.
+// (eg. sound_fragment_size==12, sound_speed==44000 means 0.093s delay)
+
+#ifdef QT_QWS_SOUND_STEREO
+static int sound_stereo=QT_QWS_SOUND_STEREO;
+#else
+static const int sound_stereo=0;
+#endif
+#ifdef QT_QWS_SOUND_16BIT
+static bool sound_16bit=QT_QWS_SOUND_16BIT;
+#else
+static const bool sound_16bit=false;
+#endif
+
+#ifndef QT_NO_QWS_SOUNDSERVER
+class QWSSoundServerClient : public QObject {
+    Q_OBJECT
+
+public:
+    QWSSoundServerClient(QWS_SOCK_BASE *s, QObject* parent);
+    ~QWSSoundServerClient();
+
+public slots:
+    void sendSoundCompleted(int, int);
+    void sendDeviceReady(int, int);
+    void sendDeviceError(int, int, int);
+
+signals:
+    void play(int, int, const QString&);
+    void play(int, int, const QString&, int, int);
+    void playRaw(int, int, const QString&, int, int, int, int);
+
+    void pause(int, int);
+    void stop(int, int);
+    void resume(int, int);
+    void setVolume(int, int, int, int);
+    void setMute(int, int, bool);
+
+    void stopAll(int);
+
+    void playPriorityOnly(bool);
+
+    void setSilent( bool );
+
+private slots:
+    void tryReadCommand();
+
+private:
+    void sendClientMessage(QString msg);
+    int mCurrentID;
+    int left, right;
+    bool priExist;
+    static int lastId;
+    static int nextId() { return ++lastId; }
+    QPointer<QWS_SOCK_BASE> socket;
+};
+
+int QWSSoundServerClient::lastId = 0;
+
+QWSSoundServerClient::QWSSoundServerClient(QWS_SOCK_BASE *s, QObject* parent) :
+    QObject( parent )
+{
+    socket = s;
+    priExist = false;
+    mCurrentID = nextId();
+    connect(socket,SIGNAL(readyRead()),
+        this,SLOT(tryReadCommand()));
+    connect(socket, SIGNAL(disconnected()), this, SLOT(deleteLater()));
+}
+
+QWSSoundServerClient::~QWSSoundServerClient()
+{
+    if (priExist)
+	playPriorityOnly(false);
+    emit stopAll(mCurrentID);
+    if (socket)
+        socket->deleteLater();
+}
+
+static QString getStringTok(QString &in)
+{
+    int pos = in.indexOf(QLatin1Char(' '));
+    QString ret;
+    if (pos > 0) {
+	ret = in.left(pos);
+	in = in.mid(pos+1);
+    } else {
+	ret = in;
+	in = QString::null;
+    }
+    return ret;
+}
+
+static int getNumTok(QString &in)
+{
+    return getStringTok(in).toInt();
+}
+
+void QWSSoundServerClient::tryReadCommand()
+{
+    while ( socket->canReadLine() ) {
+	QString l = QString::fromAscii(socket->readLine());
+	l.truncate(l.length()-1); // chomp
+	QString functionName = getStringTok(l);
+	int soundid = getNumTok(l);
+	if (functionName == QLatin1String("PLAY")) {
+	    emit play(mCurrentID, soundid, l);
+	} else if (functionName == QLatin1String("PLAYEXTEND")) {
+	    int volume = getNumTok(l);
+	    int flags = getNumTok(l);
+	    emit play(mCurrentID, soundid, l, volume, flags);
+	} else if (functionName == QLatin1String("PLAYRAW")) {
+	    int chs = getNumTok(l);
+	    int freq = getNumTok(l);
+	    int bitspersample = getNumTok(l);
+	    int flags = getNumTok(l);
+	    emit playRaw(mCurrentID, soundid, l, freq, chs, bitspersample, flags);
+	} else if (functionName == QLatin1String("PAUSE")) {
+	    emit pause(mCurrentID, soundid);
+	} else if (functionName == QLatin1String("STOP")) {
+	    emit stop(mCurrentID, soundid);
+	} else if (functionName == QLatin1String("RESUME")) {
+	    emit resume(mCurrentID, soundid);
+	} else if (functionName == QLatin1String("SETVOLUME")) {
+	    int left = getNumTok(l);
+	    int right = getNumTok(l);
+	    emit setVolume(mCurrentID, soundid, left, right);
+	} else if (functionName == QLatin1String("MUTE")) {
+	    emit setMute(mCurrentID, soundid, true);
+	} else if (functionName == QLatin1String("UNMUTE")) {
+	    emit setMute(mCurrentID, soundid, false);
+	} else if (functionName == QLatin1String("PRIORITYONLY")) {
+	    bool sPri = soundid != 0;
+	    if (sPri != priExist) {
+		priExist = sPri;
+		emit playPriorityOnly(sPri);
+	    }
+	} else if(functionName == QLatin1String("SILENT")) {
+	    emit setSilent( soundid != 0 );
+	}
+    }
+}
+
+void QWSSoundServerClient::sendClientMessage(QString msg)
+{
+#ifndef QT_NO_TEXTCODEC
+    QByteArray u = msg.toUtf8();
+#else
+    QByteArray u = msg.toLatin1();
+#endif
+    socket->write(u.data(), u.length());
+    socket->flush();
+}
+
+void QWSSoundServerClient::sendSoundCompleted(int gid, int sid)
+{
+    if (gid == mCurrentID)
+        sendClientMessage(QLatin1String("SOUNDCOMPLETED ")
+                          + QString::number(sid) + QLatin1Char('\n'));
+}
+
+void QWSSoundServerClient::sendDeviceReady(int gid, int sid)
+{
+    if (gid == mCurrentID)
+        sendClientMessage(QLatin1String("DEVICEREADY ")
+                          + QString::number(sid) + QLatin1Char('\n'));
+}
+
+void QWSSoundServerClient::sendDeviceError(int gid, int sid, int err)
+{
+    if (gid == mCurrentID)
+        sendClientMessage(QLatin1String("DEVICEERROR ")
+                          + QString::number(sid) + QLatin1Char(' ')
+                          + QString::number(err) + QLatin1Char('\n'));
+}
+#endif
+
+static const int maxVolume = 100;
+static const int runinLength = 2*sound_buffer_size;
+class QWSSoundServerProvider {
+public:
+    QWSSoundServerProvider(int w, int s)
+	: mWid(w), mSid(s), mMuted(false)
+    {
+	leftVolume = maxVolume>>1;
+	rightVolume = maxVolume>>1;
+	isPriority = false;
+        samples_due = 0;
+	max1 = max2 = out = 0;//= sound_buffer_size;
+	data = data1;
+	max = &max1;
+	sampleRunin = 0;
+	dev = -1;
+    }
+
+    virtual ~QWSSoundServerProvider() {
+    }
+
+    int groupId() const { return mWid; }
+    int soundId() const { return mSid; }
+
+    void startSampleRunin() {
+	// inteded to provide even audio return from mute/pause/dead samples.
+	//sampleRunin = runinLength; // or more?
+    }
+
+
+    void setVolume(int lv, int rv) {
+	leftVolume = qMin(maxVolume, qMax(0, lv));
+	rightVolume = qMin(maxVolume, qMax(0, rv));
+    }
+
+    void setMute(bool m) { mMuted = m; }
+    bool muted() { return mMuted; }
+
+    void setPriority(bool p) {
+	if (p != isPriority) {
+	    isPriority = p; // currently meaningless.
+	}
+    }
+
+
+    static void setPlayPriorityOnly(bool p)
+    {
+	if (p)
+	    priorityExists++;
+	else
+	    priorityExists--;
+
+	if (priorityExists < 0)
+	    qDebug("QSS: got more priority offs than ons");
+    }
+
+    // return -1 for file broken, give up.
+    // else return sampels ready for playing.
+    // argument is max samples server is looking for,
+    // in terms of current device status.
+    virtual int readySamples(int) = 0;
+
+    int getSample(int off, int bps) {
+
+        //
+        //  16-bit audio data is converted to native endian so that it can be scaled
+        //  Yes, this is ugly on a BigEndian machine
+        //  Perhaps it shouldn't be scaled at all
+        //
+        return (bps == 1) ? (data[out+off] - 128) * 128 : qToLittleEndian(((short*)data)[(out/2)+off]);
+    }
+
+    int add(int* mixl, int* mixr, int count)
+    {
+        int bytesPerSample = chunkdata.wBitsPerSample >> 3;
+
+        if ( mMuted ) {
+            sampleRunin -= qMin(sampleRunin,count);
+            while (count && (dev != -1)) {
+                if (out >= *max) {
+                    // switch buffers
+                    out = 0;
+                    if (data == data1 && max2 != 0) {
+                        data = data2;
+                        max = &max2;
+                        max1 = 0;
+                    } else if (data == data2 && max1 != 0) {
+                        data = data1;
+                        max = &max1;
+                        max2 = 0;
+                    } else {
+                        qDebug("QSS Read Error: both buffers empty");
+                        return 0;
+                    }
+                }
+                samples_due += sound_speed;
+                while (count && samples_due >= chunkdata.samplesPerSec) {
+                    samples_due -= chunkdata.samplesPerSec;
+                    count--;
+                }
+                out += bytesPerSample * chunkdata.channels;
+            }
+            return count;
+        }
+
+        // This shouldn't be the case
+        if ( !mixl || !mixr )
+            return 0;
+
+        int lVolNum = leftVolume, lVolDen = maxVolume;
+        int rVolNum = rightVolume, rVolDen = maxVolume;
+        if (priorityExists > 0 && !isPriority) {
+            lVolNum = 0; // later, make this gradually fade in and out.
+            lVolDen = 5;
+            rVolNum = 0;
+            rVolDen = 5;
+        }
+
+        while (count && (dev != -1)) {
+            if (out >= *max) {
+                // switch buffers
+                out = 0;
+                if (data == data1 && max2 != 0) {
+                    data = data2;
+                    max = &max2;
+                    max1 = 0;
+                } else if (data == data2 && max1 != 0) {
+                    data = data1;
+                    max = &max1;
+                    max2 = 0;
+                } else {
+                    qDebug("QSS Read Error: both buffers empty");
+                    return 0;
+                }
+            }
+            samples_due += sound_speed;
+            if (count && samples_due >= chunkdata.samplesPerSec) {
+                int l = getSample(0,bytesPerSample)*lVolNum/lVolDen;
+                int r = (chunkdata.channels == 2) ? getSample(1,bytesPerSample)*rVolNum/rVolDen : l;
+                if (!sound_stereo && chunkdata.channels == 2)
+                    l += r;
+		if (sampleRunin) {
+                    while (sampleRunin && count && samples_due >= chunkdata.samplesPerSec) {
+                        mixl++;
+                        if (sound_stereo)
+                            mixr++;
+                        samples_due -= chunkdata.samplesPerSec;
+		        sampleRunin--;
+                        count--;
+                    }
+                }
+                while (count && samples_due >= chunkdata.samplesPerSec) {
+                    *mixl++ += l;
+                    if (sound_stereo)
+                        *mixr++ += r;
+                    samples_due -= chunkdata.samplesPerSec;
+                    count--;
+                }
+            }
+
+            // optimize out manipulation of sample if downsampling and we skip it
+            out += bytesPerSample * chunkdata.channels;
+        }
+
+        return count;
+    }
+
+    virtual bool finished() const = 0;
+
+    bool equal(int wid, int sid)
+    {
+	return (wid == mWid && sid == mSid);
+    }
+
+protected:
+
+    char * prepareBuffer( int &size)
+    {
+	// keep reading as long as there is 50 % or more room in off buffer.
+	if (data == data1 && (max2<<1 < sound_buffer_size)) {
+	    size=sound_buffer_size - max2;
+	    return (char *)data2;
+	} else if (data == data2 && (max1<<1 < sound_buffer_size)) {
+	    size=sound_buffer_size - max1;
+	    return (char *)data1;
+	} else {
+	    size = 0;
+	    return 0;
+	}
+    }
+
+    void updateBuffer(int read)
+    {
+	// always reads to off buffer.
+	if (read >= 0) {
+	    if (data == data2) {
+		max1 = read;
+	    } else {
+		max2 = read;
+	    }
+	}
+    }
+
+    int devSamples()
+    {
+	int possible = (((max1+max2-out) / ((chunkdata.wBitsPerSample>>3)*chunkdata.channels))
+		*sound_speed)/chunkdata.samplesPerSec;
+
+	return possible;
+    }
+
+
+    struct {
+        qint16 formatTag;
+        qint16 channels;
+        qint32 samplesPerSec;
+        qint32 avgBytesPerSec;
+        qint16 blockAlign;
+        qint16 wBitsPerSample;
+    } chunkdata;
+    int dev;
+    int samples_due;
+private:
+    int mWid;
+    int mSid;
+    int leftVolume;
+    int rightVolume;
+    bool isPriority;
+    static int priorityExists;
+    int *max;
+    uchar *data;
+    uchar data1[sound_buffer_size+4]; // +4 to handle badly aligned input data
+    uchar data2[sound_buffer_size+4]; // +4 to handle badly aligned input data
+    int out, max1, max2;
+    int sampleRunin;
+    bool mMuted;
+};
+
+int QWSSoundServerProvider::priorityExists = 0;
+
+class QWSSoundServerBucket : public QWSSoundServerProvider {
+public:
+    QWSSoundServerBucket(int d, int wid, int sid)
+	: QWSSoundServerProvider(wid, sid)
+    {
+	dev = d;
+	wavedata_remaining = -1;
+	mFinishedRead = false;
+	mInsufficientSamples = false;
+    }
+    ~QWSSoundServerBucket()
+    {
+	//dev->close();
+	::close(dev);
+    }
+    bool finished() const
+    {
+	//return !max;
+	return mInsufficientSamples && mFinishedRead ;
+    }
+    int readySamples(int)
+    {
+	int size;
+	char *dest = prepareBuffer(size);
+	// may want to change this to something like
+	// if (data == data1 && max2<<1 < sound_buffer_size
+	//	||
+	//	data == data2 && max1<<1 < sound_buffer_size)
+	// so will keep filling off buffer while there is +50% space left
+	if (size > 0 && dest != 0) {
+	    while ( wavedata_remaining < 0 ) {
+		//max = 0;
+		wavedata_remaining = -1;
+		// Keep reading chunks...
+		const int n = sizeof(chunk)-sizeof(chunk.data);
+		int nr = ::read(dev, (void*)&chunk,n);
+		if ( nr != n ) {
+		    // XXX check error? or don't we care?
+		    wavedata_remaining = 0;
+		    mFinishedRead = true;
+		} else if ( qstrncmp(chunk.id,"data",4) == 0 ) {
+		    wavedata_remaining = qToLittleEndian( chunk.size );
+
+		    //out = max = sound_buffer_size;
+
+		} else if ( qstrncmp(chunk.id,"RIFF",4) == 0 ) {
+		    char d[4];
+		    if ( read(dev, d, 4) != 4 ) {
+			// XXX check error? or don't we care?
+			//qDebug("couldn't read riff");
+			mInsufficientSamples = true;
+			mFinishedRead = true;
+			return 0;
+		    } else if ( qstrncmp(d,"WAVE",4) != 0 ) {
+			// skip
+			if ( chunk.size > 1000000000 || lseek(dev,chunk.size-4, SEEK_CUR) == -1 ) {
+			    //qDebug("oversized wav chunk");
+			    mFinishedRead = true;
+			}
+		    }
+		} else if ( qstrncmp(chunk.id,"fmt ",4) == 0 ) {
+		    if ( ::read(dev,(char*)&chunkdata,sizeof(chunkdata)) != sizeof(chunkdata) ) {
+			// XXX check error? or don't we care?
+			//qDebug("couldn't ready chunkdata");
+			mFinishedRead = true;
+		    }
+
+#define WAVE_FORMAT_PCM 1
+		    else
+            {
+                /*
+                **  Endian Fix the chuck data
+                */
+                chunkdata.formatTag         = qToLittleEndian( chunkdata.formatTag );
+                chunkdata.channels          = qToLittleEndian( chunkdata.channels );
+                chunkdata.samplesPerSec     = qToLittleEndian( chunkdata.samplesPerSec );
+                chunkdata.avgBytesPerSec    = qToLittleEndian( chunkdata.avgBytesPerSec );
+                chunkdata.blockAlign        = qToLittleEndian( chunkdata.blockAlign );
+                chunkdata.wBitsPerSample    = qToLittleEndian( chunkdata.wBitsPerSample );
+                if ( chunkdata.formatTag != WAVE_FORMAT_PCM ) {
+                    qWarning("WAV file: UNSUPPORTED FORMAT %d",chunkdata.formatTag);
+                    mFinishedRead = true;
+                }
+		    }
+		} else {
+		    // ignored chunk
+		    if ( chunk.size > 1000000000 || lseek(dev, chunk.size, SEEK_CUR) == -1) {
+			//qDebug("chunk size too big");
+			mFinishedRead = true;
+		    }
+		}
+	    }
+	    // this looks wrong.
+	    if (wavedata_remaining <= 0) {
+		mFinishedRead = true;
+	    }
+
+	}
+	// may want to change this to something like
+	// if (data == data1 && max2<<1 < sound_buffer_size
+	//	||
+	//	data == data2 && max1<<1 < sound_buffer_size)
+	// so will keep filling off buffer while there is +50% space left
+
+	if (wavedata_remaining) {
+	    if (size > 0 && dest != 0) {
+		int read = ::read(dev, dest, qMin(size, wavedata_remaining));
+		// XXX check error? or don't we care?
+		wavedata_remaining -= read;
+		updateBuffer(read);
+		if (read <= 0) // data unexpectidly ended
+		    mFinishedRead = true;
+	    }
+	}
+	int possible = devSamples();
+	if (possible == 0)
+	    mInsufficientSamples = true;
+	return possible;
+    }
+
+protected:
+    QRiffChunk chunk;
+    int wavedata_remaining;
+    bool mFinishedRead;
+    bool mInsufficientSamples;
+};
+
+class QWSSoundServerStream : public QWSSoundServerProvider {
+public:
+    QWSSoundServerStream(int d,int c, int f, int b,
+	    int wid, int sid)
+	: QWSSoundServerProvider(wid, sid)
+    {
+	chunkdata.channels = c;
+	chunkdata.samplesPerSec = f;
+	chunkdata.wBitsPerSample = b;
+	dev = d;
+	//fcntl( dev, F_SETFL, O_NONBLOCK );
+	lasttime = 0;
+    }
+
+    ~QWSSoundServerStream()
+    {
+	if (dev != -1) {
+	    ::close(dev);
+	    dev = -1;
+	}
+    }
+
+    bool finished() const
+    {
+	return (dev == -1);
+    }
+
+
+    int readySamples(int)
+    {
+	int size;
+	char *dest = prepareBuffer(size);
+	if (size > 0 && dest != 0 && dev != -1) {
+
+	    int read = ::read(dev, dest, size);
+	    if (read < 0) {
+		switch(errno) {
+		    case EAGAIN:
+		    case EINTR:
+			// means read may yet succeed on the next attempt
+			break;
+		    default:
+			// unexpected error, fail.
+			::close(dev);
+			dev = -1;
+		}
+	    } else if (read == 0) {
+		// 0 means writer has closed dev and/or
+		// file is at end.
+		::close(dev);
+		dev = -1;
+	    } else {
+		updateBuffer(read);
+	    }
+	}
+	int possible = devSamples();
+	if (possible == 0)
+	    startSampleRunin();
+	return possible;
+    }
+
+protected:
+    time_t lasttime;
+};
+
+#ifndef QT_NO_QWS_SOUNDSERVER
+QWSSoundServerSocket::QWSSoundServerSocket(QObject *parent) :
+    QWSServerSocket(QT_VFB_SOUND_PIPE(qws_display_id), parent)
+{
+    connect(this, SIGNAL(newConnection()), this, SLOT(newConnection()));
+}
+
+
+#ifdef QT3_SUPPORT
+QWSSoundServerSocket::QWSSoundServerSocket(QObject *parent, const char *name) :
+    QWSServerSocket(QT_VFB_SOUND_PIPE(qws_display_id), parent)
+{
+    if (name)
+        setObjectName(QString::fromAscii(name));
+    connect(this, SIGNAL(newConnection()), this, SLOT(newConnection()));
+}
+#endif
+
+void QWSSoundServerSocket::newConnection()
+{
+    while (QWS_SOCK_BASE *sock = nextPendingConnection()) {
+        QWSSoundServerClient* client = new QWSSoundServerClient(sock,this);
+
+        connect(client, SIGNAL(play(int,int,QString)),
+                this, SIGNAL(playFile(int,int,QString)));
+        connect(client, SIGNAL(play(int,int,QString,int,int)),
+                this, SIGNAL(playFile(int,int,QString,int,int)));
+        connect(client, SIGNAL(playRaw(int,int,QString,int,int,int,int)),
+                this, SIGNAL(playRawFile(int,int,QString,int,int,int,int)));
+
+        connect(client, SIGNAL(pause(int,int)),
+                this, SIGNAL(pauseFile(int,int)));
+        connect(client, SIGNAL(stop(int,int)),
+                this, SIGNAL(stopFile(int,int)));
+        connect(client, SIGNAL(playPriorityOnly(bool)),
+                this, SIGNAL(playPriorityOnly(bool)));
+        connect(client, SIGNAL(stopAll(int)),
+                this, SIGNAL(stopAll(int)));
+        connect(client, SIGNAL(resume(int,int)),
+                this, SIGNAL(resumeFile(int,int)));
+
+        connect(client, SIGNAL(setSilent(bool)),
+                this, SIGNAL(setSilent(bool)));
+
+        connect(client, SIGNAL(setMute(int,int,bool)),
+                this, SIGNAL(setMute(int,int,bool)));
+        connect(client, SIGNAL(setVolume(int,int,int,int)),
+                this, SIGNAL(setVolume(int,int,int,int)));
+
+        connect(this, SIGNAL(soundFileCompleted(int,int)),
+                client, SLOT(sendSoundCompleted(int,int)));
+        connect(this, SIGNAL(deviceReady(int,int)),
+                client, SLOT(sendDeviceReady(int,int)));
+        connect(this, SIGNAL(deviceError(int,int,int)),
+                client, SLOT(sendDeviceError(int,int,int)));
+    }
+}
+
+#endif
+
+class QWSSoundServerPrivate : public QObject {
+    Q_OBJECT
+
+public:
+    QWSSoundServerPrivate(QObject* parent=0, const char* name=0) :
+        QObject(parent)
+    {
+	timerId = 0;
+        if (name)
+            setObjectName(QString::fromAscii(name));
+#ifndef QT_NO_QWS_SOUNDSERVER
+        server = new QWSSoundServerSocket(this);
+
+	connect(server, SIGNAL(playFile(int,int,QString)),
+		this, SLOT(playFile(int,int,QString)));
+	connect(server, SIGNAL(playFile(int,int,QString,int,int)),
+		this, SLOT(playFile(int,int,QString,int,int)));
+	connect(server, SIGNAL(playRawFile(int,int,QString,int,int,int,int)),
+		this, SLOT(playRawFile(int,int,QString,int,int,int,int)));
+
+	connect(server, SIGNAL(pauseFile(int,int)),
+		this, SLOT(pauseFile(int,int)));
+	connect(server, SIGNAL(stopFile(int,int)),
+		this, SLOT(stopFile(int,int)));
+	connect(server, SIGNAL(stopAll(int)),
+		this, SLOT(stopAll(int)));
+	connect(server, SIGNAL(playPriorityOnly(bool)),
+		this, SLOT(playPriorityOnly(bool)));
+	connect(server, SIGNAL(resumeFile(int,int)),
+		this, SLOT(resumeFile(int,int)));
+
+        connect( server, SIGNAL(setSilent(bool)),
+                this, SLOT(setSilent(bool)));
+
+        connect(server, SIGNAL(setMute(int,int,bool)),
+                this, SLOT(setMute(int,int,bool)));
+	connect(server, SIGNAL(setVolume(int,int,int,int)),
+		this, SLOT(setVolume(int,int,int,int)));
+
+	connect(this, SIGNAL(soundFileCompleted(int,int)),
+		server, SIGNAL(soundFileCompleted(int,int)));
+	connect(this, SIGNAL(deviceReady(int,int)),
+		server, SIGNAL(deviceReady(int,int)));
+	connect(this, SIGNAL(deviceError(int,int,int)),
+		server, SIGNAL(deviceError(int,int,int)));
+
+#endif
+        silent = false;
+        fd = -1;
+        unwritten = 0;
+        can_GETOSPACE = true;
+    }
+
+    ~QWSSoundServerPrivate()
+    {
+        qDeleteAll(active);
+        qDeleteAll(inactive);
+    }
+
+signals:
+    void soundFileCompleted(int, int);
+    void deviceReady(int, int);
+    void deviceError(int, int, int);
+
+public slots:
+    void playRawFile(int wid, int sid, const QString &filename, int freq, int channels, int bitspersample, int flags);
+    void playFile(int wid, int sid, const QString& filename);
+    void playFile(int wid, int sid, const QString& filename, int v, int flags);
+    void checkPresetVolumes(int wid, int sid, QWSSoundServerProvider *p);
+    void pauseFile(int wid, int sid);
+    void resumeFile(int wid, int sid);
+    void stopFile(int wid, int sid);
+    void stopAll(int wid);
+    void setVolume(int wid, int sid, int lv, int rv);
+    void setMute(int wid, int sid, bool m);
+    void playPriorityOnly(bool p);
+    void sendCompletedSignals();
+    void feedDevice(int fd);
+    void setSilent( bool enabled );
+
+protected:
+    void timerEvent(QTimerEvent* event);
+
+private:
+    int openFile(int wid, int sid, const QString& filename);
+    bool openDevice();
+    void closeDevice()
+    {
+        if (fd >= 0) {
+            ::close(fd);
+            fd = -1;
+        }
+    }
+
+    QList<QWSSoundServerProvider*> active;
+    QList<QWSSoundServerProvider*> inactive;
+    struct PresetVolume {
+	int wid;
+	int sid;
+	int left;
+	int right;
+	bool mute;
+    };
+    QList<PresetVolume> volumes;
+    struct CompletedInfo {
+	CompletedInfo( ) : groupId( 0 ), soundId( 0 ) { }
+	CompletedInfo( int _groupId, int _soundId ) : groupId( _groupId ), soundId( _soundId ) { }
+	int groupId;
+	int soundId;
+    };
+    QList<CompletedInfo> completed;
+
+    bool silent;
+
+    int fd;
+    int unwritten;
+    int timerId;
+    char* cursor;
+    short data[sound_buffer_size*2];
+    bool can_GETOSPACE;
+#ifndef QT_NO_QWS_SOUNDSERVER
+    QWSSoundServerSocket *server;
+#endif
+};
+
+void QWSSoundServerPrivate::setSilent( bool enabled )
+{
+    // Close output device
+    closeDevice();
+    if( !unwritten && !active.count() ) {
+        sendCompletedSignals();
+    }
+    // Stop processing audio
+    killTimer( timerId );
+    silent = enabled;
+    // If audio remaining, open output device and continue processing
+    if( unwritten || active.count() ) {
+        openDevice();
+    }
+}
+
+void QWSSoundServerPrivate::timerEvent(QTimerEvent* event)
+{
+    // qDebug("QSS timer event");
+    if( event->timerId() == timerId ) {
+        if (fd >= 0)
+            feedDevice(fd);
+        if (fd < 0) {
+            killTimer(timerId);
+            timerId = 0;
+        }
+    }
+}
+
+void QWSSoundServerPrivate::playRawFile(int wid, int sid, const QString &filename,
+                                        int freq, int channels, int bitspersample, int flags)
+{
+#ifdef QT_NO_QWS_SOUNDSERVER
+    Q_UNUSED(flags);
+#endif
+    int f = openFile(wid, sid, filename);
+    if ( f ) {
+        QWSSoundServerStream *b = new QWSSoundServerStream(f, channels, freq, bitspersample, wid, sid);
+        // check preset volumes.
+        checkPresetVolumes(wid, sid, b);
+#ifndef QT_NO_QWS_SOUNDSERVER
+        b->setPriority((flags & QWSSoundClient::Priority) == QWSSoundClient::Priority);
+#endif
+        active.append(b);
+        emit deviceReady(wid, sid);
+    }
+}
+
+void QWSSoundServerPrivate::playFile(int wid, int sid, const QString& filename)
+{
+    int f = openFile(wid, sid, filename);
+    if ( f ) {
+        QWSSoundServerProvider *b = new QWSSoundServerBucket(f, wid, sid);
+        checkPresetVolumes(wid, sid, b);
+        active.append( b );
+        emit deviceReady(wid, sid);
+    }
+}
+
+void QWSSoundServerPrivate::playFile(int wid, int sid, const QString& filename,
+                                     int v, int flags)
+{
+#ifdef QT_NO_QWS_SOUNDSERVER
+    Q_UNUSED(flags);
+#endif
+    int f = openFile(wid, sid, filename);
+    if ( f ) {
+        QWSSoundServerProvider *b = new QWSSoundServerBucket(f, wid, sid);
+        checkPresetVolumes(wid, sid, b);
+        b->setVolume(v, v);
+#ifndef QT_NO_QWS_SOUNDSERVER
+        b->setPriority((flags & QWSSoundClient::Priority) == QWSSoundClient::Priority);
+#endif
+        active.append(b);
+        emit deviceReady(wid, sid);
+    }
+}
+
+void QWSSoundServerPrivate::checkPresetVolumes(int wid, int sid, QWSSoundServerProvider *p)
+{
+    QList<PresetVolume>::Iterator it = volumes.begin();
+    while (it != volumes.end()) {
+        PresetVolume v = *it;
+        if (v.wid == wid && v.sid == sid) {
+            p->setVolume(v.left, v.right);
+            p->setMute(v.mute);
+            it = volumes.erase(it);
+            return;
+        } else {
+            ++it;
+        }
+    }
+}
+
+void QWSSoundServerPrivate::pauseFile(int wid, int sid)
+{
+    QWSSoundServerProvider *bucket;
+    for (int i = 0; i < active.size(); ++i ) {
+        bucket = active.at(i);
+        if (bucket->equal(wid, sid)) {
+            // found bucket....
+            active.removeAt(i);
+            inactive.append(bucket);
+            return;
+        }
+    }
+}
+
+void QWSSoundServerPrivate::resumeFile(int wid, int sid)
+{
+    QWSSoundServerProvider *bucket;
+    for (int i = 0; i < inactive.size(); ++i ) {
+        bucket = inactive.at(i);
+        if (bucket->equal(wid, sid)) {
+            // found bucket....
+            inactive.removeAt(i);
+            active.append(bucket);
+            return;
+        }
+    }
+}
+
+void QWSSoundServerPrivate::stopFile(int wid, int sid)
+{
+    QWSSoundServerProvider *bucket;
+    for (int i = 0; i < active.size(); ++i ) {
+        bucket = active.at(i);
+        if (bucket->equal(wid, sid)) {
+            active.removeAt(i);
+            delete bucket;
+            return;
+        }
+    }
+    for (int i = 0; i < inactive.size(); ++i ) {
+        bucket = inactive.at(i);
+        if (bucket->equal(wid, sid)) {
+            inactive.removeAt(i);
+            delete bucket;
+            return;
+        }
+    }
+}
+
+void QWSSoundServerPrivate::stopAll(int wid)
+{
+    QWSSoundServerProvider *bucket;
+    if (!active.isEmpty()) {
+        QList<QWSSoundServerProvider*>::Iterator it = active.begin();
+        while (it != active.end()) {
+            bucket = *it;
+            if (bucket->groupId() == wid) {
+                it = active.erase(it);
+                delete bucket;
+            } else {
+                ++it;
+            }
+        }
+    }
+    if (!inactive.isEmpty()) {
+        QList<QWSSoundServerProvider*>::Iterator it = inactive.begin();
+        while (it != inactive.end()) {
+            bucket = *it;
+            if (bucket->groupId() == wid) {
+                it = inactive.erase(it);
+                delete bucket;
+            } else {
+                ++it;
+            }
+        }
+    }
+}
+
+void QWSSoundServerPrivate::setVolume(int wid, int sid, int lv, int rv)
+{
+    QWSSoundServerProvider *bucket;
+    for( int i = 0; i < active.size(); ++i ) {
+        bucket = active.at(i);
+        if (bucket->equal(wid, sid)) {
+            bucket->setVolume(lv,rv);
+            return;
+        }
+    }
+    // If gotten here, then it means wid/sid wasn't set up yet.
+    // first find and remove current preset volumes, then add this one.
+    QList<PresetVolume>::Iterator it = volumes.begin();
+    while (it != volumes.end()) {
+        PresetVolume v = *it;
+        if (v.wid == wid && v.sid == sid)
+            it = volumes.erase(it);
+        else
+            ++it;
+    }
+    // and then add this volume
+    PresetVolume nv;
+    nv.wid = wid;
+    nv.sid = sid;
+    nv.left = lv;
+    nv.right = rv;
+    nv.mute = false;
+    volumes.append(nv);
+}
+
+void QWSSoundServerPrivate::setMute(int wid, int sid, bool m)
+{
+    QWSSoundServerProvider *bucket;
+    for( int i = 0; i < active.size(); ++i ) {
+        bucket = active.at(i);
+        if (bucket->equal(wid, sid)) {
+            bucket->setMute(m);
+            return;
+        }
+    }
+    // if gotten here then setting is being applied before item
+    // is created.
+    QList<PresetVolume>::Iterator it = volumes.begin();
+    while (it != volumes.end()) {
+        PresetVolume v = *it;
+        if (v.wid == wid && v.sid == sid) {
+            (*it).mute = m;
+            return;
+        }
+    }
+    if (m) {
+        PresetVolume nv;
+        nv.wid = wid;
+        nv.sid = sid;
+        nv.left = maxVolume>>1;
+        nv.right = maxVolume>>1;
+        nv.mute = true;
+        volumes.append(nv);
+    }
+}
+
+void QWSSoundServerPrivate::playPriorityOnly(bool p)
+{
+    QWSSoundServerProvider::setPlayPriorityOnly(p);
+}
+
+void QWSSoundServerPrivate::sendCompletedSignals()
+{
+    while( !completed.isEmpty() ) {
+        emit soundFileCompleted( (*completed.begin()).groupId,
+            (*completed.begin()).soundId );
+            completed.erase( completed.begin() );
+    }
+}
+
+
+int QWSSoundServerPrivate::openFile(int wid, int sid, const QString& filename)
+{
+    stopFile(wid, sid); // close and re-open.
+    int f = QT_OPEN(QFile::encodeName(filename), O_RDONLY|O_NONBLOCK);
+    if (f == -1) {
+        // XXX check ferror, check reason.
+        qDebug("Failed opening \"%s\"",filename.toLatin1().data());
+#ifndef QT_NO_QWS_SOUNDSERVER
+        emit deviceError(wid, sid, (int)QWSSoundClient::ErrOpeningFile );
+#endif
+    } else if ( openDevice() ) {
+        return f;
+    }
+#ifndef QT_NO_QWS_SOUNDSERVER
+    emit deviceError(wid, sid, (int)QWSSoundClient::ErrOpeningAudioDevice );
+#endif
+    return 0;
+}
+
+bool QWSSoundServerPrivate::openDevice()
+{
+        if (fd < 0) {
+            if( silent ) {
+                fd = QT_OPEN( "/dev/null", O_WRONLY );
+                // Emulate write to audio device
+                int delay = 1000*(sound_buffer_size>>(sound_stereo+sound_16bit))/sound_speed/2;
+                timerId = startTimer(delay);
+
+                return true;
+            }
+            //
+            // Don't block open right away.
+            //
+            bool openOkay = false;
+            if ((fd = QT_OPEN("/dev/dsp", O_WRONLY|O_NONBLOCK)) != -1) {
+                int flags = fcntl(fd, F_GETFL);
+                flags &= ~O_NONBLOCK;
+		openOkay = (fcntl(fd, F_SETFL, flags) == 0);
+	    }
+            if (!openOkay) {
+	        qDebug("Failed opening audio device");
+		return false;
+            }
+
+            // Setup soundcard at 16 bit mono
+            int v;
+	    //v=0x00010000+sound_fragment_size;
+	    // um the media player did this instead.
+	    v=0x10000 * 4 + sound_fragment_size;
+            if (ioctl(fd, SNDCTL_DSP_SETFRAGMENT, &v))
+                qWarning("Could not set fragments to %08x",v);
+#ifdef QT_QWS_SOUND_16BIT
+            //
+            //  Use native endian
+            //  Since we have manipulated the data volume the data
+            //  is now in native format, even though its stored
+            //  as little endian in the WAV file
+            //
+            v=AFMT_S16_NE; if (ioctl(fd, SNDCTL_DSP_SETFMT, &v))
+                qWarning("Could not set format %d",v);
+            if (AFMT_S16_NE != v)
+                qDebug("Want format %d got %d", AFMT_S16_LE, v);
+#else
+            v=AFMT_U8; if (ioctl(fd, SNDCTL_DSP_SETFMT, &v))
+                qWarning("Could not set format %d",v);
+            if (AFMT_U8 != v)
+                qDebug("Want format %d got %d", AFMT_U8, v);
+#endif
+            v=sound_stereo; if (ioctl(fd, SNDCTL_DSP_STEREO, &v))
+                qWarning("Could not set stereo %d",v);
+            if (sound_stereo != v)
+                qDebug("Want stereo %d got %d", sound_stereo, v);
+#ifdef QT_QWS_SOUND_STEREO
+            sound_stereo=v;
+#endif
+            v=sound_speed; if (ioctl(fd, SNDCTL_DSP_SPEED, &sound_speed))
+                qWarning("Could not set speed %d",v);
+            if (v != sound_speed)
+                qDebug("Want speed %d got %d", v, sound_speed);
+
+            int delay = 1000*(sound_buffer_size>>(sound_stereo+sound_16bit))
+                                    /sound_speed/2;
+	    // qDebug("QSS delay: %d", delay);
+            timerId = startTimer(delay);
+
+	    //
+	    // Check system volume
+	    //
+	    int mixerHandle = QT_OPEN( "/dev/mixer", O_RDWR|O_NONBLOCK );
+	    if ( mixerHandle >= 0 ) {
+		int volume;
+		ioctl( mixerHandle, MIXER_READ(0), &volume );
+		close( mixerHandle );
+		if ( volume < 1<<(sound_stereo+sound_16bit) )
+		    qDebug("Want sound at %d got %d",
+			    1<<(sound_stereo+sound_16bit), volume);
+	    } else
+		qDebug( "get volume of audio device failed" );
+
+        }
+        return true;
+}
+
+void  QWSSoundServerPrivate::feedDevice(int fd)
+{
+    if ( !unwritten && active.size() == 0 ) {
+        closeDevice();
+        sendCompletedSignals();
+        return;
+    } else {
+        sendCompletedSignals();
+    }
+
+    QWSSoundServerProvider* bucket;
+
+    // find out how much audio is possible
+    int available = sound_buffer_size;
+    QList<QWSSoundServerProvider*> running;
+    for (int i = 0; i < active.size(); ++i) {
+        bucket = active.at(i);
+        int ready = bucket->readySamples(available);
+        if (ready > 0) {
+            available = qMin(available, ready);
+            running.append(bucket);
+        }
+    }
+
+    audio_buf_info info;
+    if (can_GETOSPACE && ioctl(fd,SNDCTL_DSP_GETOSPACE,&info)) {
+        can_GETOSPACE = false;
+        fcntl(fd, F_SETFL, O_NONBLOCK);
+    }
+    if (!can_GETOSPACE)
+        info.fragments = 4; // #### configurable?
+    if (info.fragments > 0) {
+        if (!unwritten) {
+            int left[sound_buffer_size];
+            memset(left,0,available*sizeof(int));
+            int right[sound_buffer_size];
+            if ( sound_stereo )
+                memset(right,0,available*sizeof(int));
+
+            if (running.size() > 0) {
+            // should do volume mod here in regards to each bucket to avoid flattened/bad peaks.
+                for (int i = 0; i < running.size(); ++i ) {
+                    bucket = running.at(i);
+                    int unused = bucket->add(left,right,available);
+                    if (unused > 0) {
+                        // this error is quite serious, as
+                        // it will really screw up mixing.
+                        qDebug("provider lied about samples ready");
+                    }
+                }
+                if ( sound_16bit ) {
+                    short *d = (short*)data;
+                    for (int i=0; i<available; i++) {
+                        *d++ = (short)qMax(qMin(left[i],32767),-32768);
+                        if ( sound_stereo )
+                            *d++ = (short)qMax(qMin(right[i],32767),-32768);
+                    }
+                } else {
+                    signed char *d = (signed char *)data;
+                    for (int i=0; i<available; i++) {
+                        *d++ = (signed char)qMax(qMin(left[i]/256,127),-128)+128;
+                        if ( sound_stereo )
+                            *d++ = (signed char)qMax(qMin(right[i]/256,127),-128)+128;
+                    }
+                }
+                unwritten = available*(sound_16bit+1)*(sound_stereo+1);
+                cursor = (char*)data;
+            }
+        }
+        // sound open, but nothing written.  Should clear the buffer.
+
+        int w;
+        if (unwritten) {
+            w = ::write(fd,cursor,unwritten);
+
+            if (w < 0) {
+                if (can_GETOSPACE)
+                    return;
+                w = 0;
+            }
+
+            cursor += w;
+            unwritten -= w;
+        } else {
+            // write some zeros to clear the buffer?
+            if (!zeroMem)
+                zeroMem = (char *)calloc(sound_buffer_size, sizeof(char));
+            w = ::write(fd, zeroMem, sound_buffer_size);
+            if (w < 0)
+                w = 0;
+        }
+    }
+
+    QList<QWSSoundServerProvider*>::Iterator it = active.begin();
+    while (it != active.end()) {
+        bucket = *it;
+        if (bucket->finished()) {
+            completed.append(CompletedInfo(bucket->groupId(), bucket->soundId()));
+            it = active.erase(it);
+            delete bucket;
+        } else {
+            ++it;
+        }
+    }
+}
+
+
+QWSSoundServer::QWSSoundServer(QObject* parent) :
+    QObject(parent)
+{
+    d = new QWSSoundServerPrivate(this);
+
+    connect( d, SIGNAL(soundFileCompleted(int,int)),
+        this, SLOT(translateSoundCompleted(int,int)) );
+}
+
+void QWSSoundServer::playFile( int sid, const QString& filename )
+{
+    //wid == 0, as it is the server initiating rather than a client
+    // if wid was passable, would accidently collide with server
+    // sockect's wids.
+    d->playFile(0, sid, filename);
+}
+
+void QWSSoundServer::pauseFile( int sid )
+{
+    d->pauseFile(0, sid);
+}
+
+void QWSSoundServer::stopFile( int sid )
+{
+    d->stopFile(0, sid);
+}
+
+void QWSSoundServer::resumeFile( int sid )
+{
+    d->resumeFile(0, sid);
+}
+
+QWSSoundServer::~QWSSoundServer()
+{
+    d->stopAll(0);
+}
+
+void QWSSoundServer::translateSoundCompleted( int, int sid )
+{
+    emit soundCompleted( sid );
+}
+
+#ifndef QT_NO_QWS_SOUNDSERVER
+QWSSoundClient::QWSSoundClient(QObject* parent) :
+    QWSSocket(parent)
+{
+    connectToLocalFile(QT_VFB_SOUND_PIPE(qws_display_id));
+    QObject::connect(this,SIGNAL(readyRead()),
+	this,SLOT(tryReadCommand()));
+    if( state() == QWS_SOCK_BASE::ConnectedState ) QTimer::singleShot(1, this, SIGNAL(connected()));
+    else QTimer::singleShot(1, this, SLOT(emitConnectionRefused()));
+}
+
+QWSSoundClient::~QWSSoundClient( )
+{
+    flush();
+}
+
+void QWSSoundClient::reconnect()
+{
+    connectToLocalFile(QT_VFB_SOUND_PIPE(qws_display_id));
+    if( state() == QWS_SOCK_BASE::ConnectedState ) emit connected();
+    else emit error( QTcpSocket::ConnectionRefusedError );
+}
+
+void QWSSoundClient::sendServerMessage(QString msg)
+{
+#ifndef QT_NO_TEXTCODEC
+    QByteArray u = msg.toUtf8();
+#else
+    QByteArray u = msg.toLatin1();
+#endif
+    write(u.data(), u.length());
+    flush();
+}
+
+void QWSSoundClient::play( int id, const QString& filename )
+{
+    QFileInfo fi(filename);
+    sendServerMessage(QLatin1String("PLAY ")
+                      + QString::number(id) + QLatin1Char(' ')
+                      + fi.absoluteFilePath() + QLatin1Char('\n'));
+}
+
+void QWSSoundClient::play( int id, const QString& filename, int volume, int flags)
+{
+    QFileInfo fi(filename);
+    sendServerMessage(QLatin1String("PLAYEXTEND ")
+        + QString::number(id) + QLatin1Char(' ')
+        + QString::number(volume) + QLatin1Char(' ')
+        + QString::number(flags) + QLatin1Char(' ')
+        + fi.absoluteFilePath() + QLatin1Char('\n'));
+}
+
+void QWSSoundClient::pause( int id )
+{
+    sendServerMessage(QLatin1String("PAUSE ")
+        + QString::number(id) + QLatin1Char('\n'));
+}
+
+void QWSSoundClient::stop( int id )
+{
+    sendServerMessage(QLatin1String("STOP ")
+        + QString::number(id) + QLatin1Char('\n'));
+}
+
+void QWSSoundClient::resume( int id )
+{
+    sendServerMessage(QLatin1String("RESUME ")
+        + QString::number(id) + QLatin1Char('\n'));
+}
+
+void QWSSoundClient::playRaw( int id, const QString& filename,
+	int freq, int chs, int bitspersample, int flags)
+{
+    QFileInfo fi(filename);
+    sendServerMessage(QLatin1String("PLAYRAW ")
+        + QString::number(id) + QLatin1Char(' ')
+        + QString::number(chs) + QLatin1Char(' ')
+        + QString::number(freq) + QLatin1Char(' ')
+        + QString::number(bitspersample) + QLatin1Char(' ')
+        + QString::number(flags) + QLatin1Char(' ')
+        + fi.absoluteFilePath() + QLatin1Char('\n'));
+}
+
+void QWSSoundClient::setMute( int id, bool m )
+{
+    sendServerMessage(QLatin1String(m ? "MUTE " : "UNMUTE ")
+        + QString::number(id) + QLatin1Char('\n'));
+}
+
+void QWSSoundClient::setVolume( int id, int leftVol, int rightVol )
+{
+    sendServerMessage(QLatin1String("SETVOLUME ")
+        + QString::number(id) + QLatin1Char(' ')
+        + QString::number(leftVol) + QLatin1Char(' ')
+        + QString::number(rightVol) + QLatin1Char('\n'));
+}
+
+void QWSSoundClient::playPriorityOnly( bool pri )
+{
+    sendServerMessage(QLatin1String("PRIORITYONLY ")
+        + QString::number(pri ? 1 : 0) + QLatin1Char('\n'));
+}
+
+void QWSSoundClient::setSilent( bool enable )
+{
+    sendServerMessage(QLatin1String("SILENT ")
+            + QString::number( enable ? 1 : 0 ) + QLatin1Char('\n'));
+}
+
+void QWSSoundClient::tryReadCommand()
+{
+    while ( canReadLine() ) {
+	QString l = QString::fromAscii(readLine());
+	l.truncate(l.length()-1); // chomp
+	QStringList token = l.split(QLatin1Char(' '));
+	if (token[0] == QLatin1String("SOUNDCOMPLETED")) {
+	    emit soundCompleted(token[1].toInt());
+	} else if (token[0] == QLatin1String("DEVICEREADY")) {
+            emit deviceReady(token[1].toInt());
+	} else if (token[0] == QLatin1String("DEVICEERROR")) {
+            emit deviceError(token[1].toInt(),(DeviceErrors)token[2].toInt());
+	}
+    }
+}
+
+void QWSSoundClient::emitConnectionRefused()
+{
+    emit error( QTcpSocket::ConnectionRefusedError );
+}
+#endif
+
+QT_END_NAMESPACE
+
+#include "qsoundqss_qws.moc"
+
+#endif        // QT_NO_SOUND