diff -r 000000000000 -r 1918ee327afb src/gui/embedded/qsoundqss_qws.cpp --- /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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include // overrides QT_OPEN + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +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 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 active; + QList inactive; + struct PresetVolume { + int wid; + int sid; + int left; + int right; + bool mute; + }; + QList volumes; + struct CompletedInfo { + CompletedInfo( ) : groupId( 0 ), soundId( 0 ) { } + CompletedInfo( int _groupId, int _soundId ) : groupId( _groupId ), soundId( _soundId ) { } + int groupId; + int soundId; + }; + QList 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::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::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::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::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::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 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::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