/**************************************************************************** ** ** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). ** 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 either Technology Preview License Agreement or the ** Beta Release License Agreement. ** ** 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.0, included in the file LGPL_EXCEPTION.txt in this ** package. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 3.0 as published by the Free Software ** Foundation and appearing in the file LICENSE.GPL included in the ** packaging of this file. Please review the following information to ** ensure the GNU General Public License version 3.0 requirements will be ** met: http://www.gnu.org/copyleft/gpl.html. ** ** If you are unsure which license is appropriate for your use, please ** contact the sales department at http://qt.nokia.com/contact. ** $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 QT_BEGIN_NAMESPACE extern int errno; #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; #define SOUND_PIPE "/tmp/.qt_soundserver-%1" #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(QString::fromLatin1(SOUND_PIPE).arg(qws_display_id), parent) { connect(this, SIGNAL(newConnection()), this, SLOT(newConnection())); } #ifdef QT3_SUPPORT QWSSoundServerSocket::QWSSoundServerSocket(QObject *parent, const char *name) : QWSServerSocket(QString::fromLatin1(SOUND_PIPE).arg(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; QList::Iterator it = active.begin(); while (it != active.end()) { bucket = *it; if (bucket->groupId() == wid) { it = active.erase(it); delete bucket; } else { ++it; } } 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(QString::fromLatin1(SOUND_PIPE).arg(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(QString::fromLatin1(SOUND_PIPE).arg(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