/////////////////////////////////////////////////////////////////////////////
-// Name: sound.cpp
+// Name: src/unix/sound.cpp
// Purpose: wxSound
// Author: Marcel Rasche, Vaclav Slavik
// Modified by:
// Created: 25/10/98
// RCS-ID: $Id$
-// Copyright: (c) Julian Smart, Vaclav Slavik
-// Licence: wxWindows licence
+// Copyright: (c) Julian Smart, Open Source Applications Foundation
+// Licence: wxWindows licence
/////////////////////////////////////////////////////////////////////////////
-#if defined(__GNUG__) && !defined(NO_GCC_PRAGMA)
-#pragma implementation "sound.h"
-#pragma implementation "soundbase.h"
-#endif
-
// for compilers that support precompilation, includes "wx.h".
#include "wx/wxprec.h"
-#include "wx/setup.h"
-
#if defined(__BORLANDC__)
-#pragma hdrstop
+ #pragma hdrstop
#endif
-#if wxUSE_WAVE
+#if wxUSE_SOUND
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>
-#if HAVE_SYS_SOUNDCARD_H
+#ifdef HAVE_SYS_SOUNDCARD_H
#include <sys/soundcard.h>
#endif
#include "wx/event.h"
#include "wx/intl.h"
#include "wx/log.h"
+ #include "wx/module.h"
#endif
#include "wx/thread.h"
#include "wx/file.h"
-#include "wx/module.h"
#include "wx/sound.h"
#include "wx/dynlib.h"
+
+#if wxUSE_THREADS
+// mutex for all wxSound's synchronization
+static wxMutex gs_soundMutex;
+#endif
+
+// ----------------------------------------------------------------------------
+// wxSoundData
+// ----------------------------------------------------------------------------
+
+void wxSoundData::IncRef()
+{
+#if wxUSE_THREADS
+ wxMutexLocker locker(gs_soundMutex);
+#endif
+ m_refCnt++;
+}
+
+void wxSoundData::DecRef()
+{
+#if wxUSE_THREADS
+ wxMutexLocker locker(gs_soundMutex);
+#endif
+ if (--m_refCnt == 0)
+ delete this;
+}
+
+wxSoundData::~wxSoundData()
+{
+ delete[] m_dataWithHeader;
+}
+
+
// ----------------------------------------------------------------------------
// wxSoundBackendNull, used in absence of audio API or card
// ----------------------------------------------------------------------------
int GetPriority() const { return 0; }
bool IsAvailable() const { return true; }
bool HasNativeAsyncPlayback() const { return true; }
- bool Play(wxSoundData *WXUNUSED(data), unsigned WXUNUSED(flags))
+ bool Play(wxSoundData *WXUNUSED(data), unsigned WXUNUSED(flags),
+ volatile wxSoundPlaybackStatus *WXUNUSED(status))
{ return true; }
+ void Stop() {}
+ bool IsPlaying() const { return false; }
};
class wxSoundBackendOSS : public wxSoundBackend
{
public:
- wxString GetName() const { return _T("Open Sound System"); }
+ wxString GetName() const { return wxT("Open Sound System"); }
int GetPriority() const { return 10; }
bool IsAvailable() const;
bool HasNativeAsyncPlayback() const { return false; }
- bool Play(wxSoundData *data, unsigned flags);
+ bool Play(wxSoundData *data, unsigned flags,
+ volatile wxSoundPlaybackStatus *status);
+ void Stop() {}
+ bool IsPlaying() const { return false; }
private:
int OpenDSP(const wxSoundData *data);
- bool InitDSP(int dev, int iDataBits, int iChannel,
- unsigned long ulSamplingRate);
-
+ bool InitDSP(int dev, const wxSoundData *data);
+
int m_DSPblkSize; // Size of the DSP buffer
+ bool m_needConversion;
};
bool wxSoundBackendOSS::IsAvailable() const
return true;
}
-bool wxSoundBackendOSS::Play(wxSoundData *data, unsigned flags)
+bool wxSoundBackendOSS::Play(wxSoundData *data, unsigned flags,
+ volatile wxSoundPlaybackStatus *status)
{
int dev = OpenDSP(data);
-
+
if (dev < 0)
return false;
ioctl(dev, SNDCTL_DSP_SYNC, 0);
-
+
do
{
bool play = true;
do
{
+ if (status->m_stopRequested)
+ {
+ wxLogTrace(wxT("sound"), wxT("playback stopped"));
+ close(dev);
+ return true;
+ }
+
i= (int)((l + m_DSPblkSize) < datasize ?
- m_DSPblkSize : (datasize - l));
+ m_DSPblkSize : (datasize - l));
if (write(dev, &data->m_data[l], i) != i)
{
play = false;
int wxSoundBackendOSS::OpenDSP(const wxSoundData *data)
{
int dev = -1;
-
+
if ((dev = open(AUDIODEV, O_WRONLY, 0)) <0)
return -1;
-
- if (!InitDSP(dev,
- (int)data->m_bitsPerSample,
- data->m_channels == 1 ? 0 : 1,
- data->m_samplingRate))
+
+ if (!InitDSP(dev, data) || m_needConversion)
{
close(dev);
return -1;
return dev;
}
-bool wxSoundBackendOSS::InitDSP(int dev, int iDataBits, int iChannel,
- unsigned long ulSamplingRate)
+
+bool wxSoundBackendOSS::InitDSP(int dev, const wxSoundData *data)
{
- if (ioctl(dev, SNDCTL_DSP_GETBLKSIZE, &m_DSPblkSize) < 0)
- return false;
- if (m_DSPblkSize < 4096 || m_DSPblkSize > 65536)
- return false;
- if (ioctl(dev, SNDCTL_DSP_SAMPLESIZE, &iDataBits) < 0)
- return false;
- if (ioctl(dev, SNDCTL_DSP_STEREO, &iChannel) < 0)
- return false;
- if (ioctl(dev, SNDCTL_DSP_SPEED, &ulSamplingRate) < 0)
+ unsigned tmp;
+
+ // Reset the dsp
+ if (ioctl(dev, SNDCTL_DSP_RESET, 0) < 0)
+ {
+ wxLogTrace(wxT("sound"), wxT("unable to reset dsp"));
return false;
- return true;
-}
+ }
-#endif // HAVE_SYS_SOUNDCARD_H
+ m_needConversion = false;
+ tmp = data->m_bitsPerSample;
+ if (ioctl(dev, SNDCTL_DSP_SAMPLESIZE, &tmp) < 0)
+ {
+ wxLogTrace(wxT("sound"), wxT("IOCTL failure (SNDCTL_DSP_SAMPLESIZE)"));
+ return false;
+ }
+ if (tmp != data->m_bitsPerSample)
+ {
+ wxLogTrace(wxT("sound"),
+ wxT("Unable to set DSP sample size to %d (wants %d)"),
+ data->m_bitsPerSample, tmp);
+ m_needConversion = true;
+ }
-// ----------------------------------------------------------------------------
-// wxSoundData
-// ----------------------------------------------------------------------------
-
-void wxSoundData::IncRef()
-{
- m_refCnt++;
-}
+ unsigned stereo = data->m_channels == 1 ? 0 : 1;
+ tmp = stereo;
+ if (ioctl(dev, SNDCTL_DSP_STEREO, &tmp) < 0)
+ {
+ wxLogTrace(wxT("sound"), wxT("IOCTL failure (SNDCTL_DSP_STEREO)"));
+ return false;
+ }
+ if (tmp != stereo)
+ {
+ wxLogTrace(wxT("sound"), wxT("Unable to set DSP to %s."), stereo? wxT("stereo"):wxT("mono"));
+ m_needConversion = true;
+ }
-void wxSoundData::DecRef()
-{
- if (--m_refCnt == 0)
- delete this;
-}
+ tmp = data->m_samplingRate;
+ if (ioctl(dev, SNDCTL_DSP_SPEED, &tmp) < 0)
+ {
+ wxLogTrace(wxT("sound"), wxT("IOCTL failure (SNDCTL_DSP_SPEED)"));
+ return false;
+ }
+ if (tmp != data->m_samplingRate)
+ {
+ // If the rate the sound card is using is not within 1% of what the
+ // data specified then override the data setting. The only reason not
+ // to always override this is because of clock-rounding
+ // problems. Sound cards will sometimes use things like 44101 when you
+ // ask for 44100. No need overriding this and having strange output
+ // file rates for something that we can't hear anyways.
+ if (data->m_samplingRate - tmp > (tmp * .01) ||
+ tmp - data->m_samplingRate > (tmp * .01)) {
+ wxLogTrace(wxT("sound"),
+ wxT("Unable to set DSP sampling rate to %d (wants %d)"),
+ data->m_samplingRate, tmp);
+ m_needConversion = true;
+ }
+ }
-wxSoundData::~wxSoundData()
-{
- delete[] m_dataWithHeader;
+ // Do this last because some drivers can adjust the buffer sized based on
+ // the sampling rate, etc.
+ if (ioctl(dev, SNDCTL_DSP_GETBLKSIZE, &m_DSPblkSize) < 0)
+ {
+ wxLogTrace(wxT("sound"), wxT("IOCTL failure (SNDCTL_DSP_GETBLKSIZE)"));
+ return false;
+ }
+ return true;
}
+#endif // HAVE_SYS_SOUNDCARD_H
// ----------------------------------------------------------------------------
-// wxSoundAsyncPlaybackThread
+// wxSoundSyncOnlyAdaptor
// ----------------------------------------------------------------------------
#if wxUSE_THREADS
-// mutex for all wxSound's synchronization
-static wxMutex gs_soundMutex;
+class wxSoundSyncOnlyAdaptor;
// this class manages asynchronous playback of audio if the backend doesn't
// support it natively (e.g. OSS backend)
class wxSoundAsyncPlaybackThread : public wxThread
{
public:
- wxSoundAsyncPlaybackThread(wxSoundBackend *backend,
+ wxSoundAsyncPlaybackThread(wxSoundSyncOnlyAdaptor *adaptor,
wxSoundData *data, unsigned flags)
- : wxThread(), m_backend(backend), m_data(data), m_flags(flags) {}
- virtual ExitCode Entry()
- {
- m_backend->Play(m_data, m_flags & ~wxSOUND_ASYNC);
- wxMutexLocker locker(gs_soundMutex);
- m_data->DecRef();
- wxLogTrace(_T("sound"), _T("terminated async playback thread"));
- return 0;
- }
-
+ : wxThread(), m_adapt(adaptor), m_data(data), m_flags(flags) {}
+ virtual ExitCode Entry();
+
protected:
- wxSoundBackend *m_backend;
+ wxSoundSyncOnlyAdaptor *m_adapt;
wxSoundData *m_data;
unsigned m_flags;
};
#endif // wxUSE_THREADS
+// This class turns wxSoundBackend that doesn't support asynchronous playback
+// into one that does
+class wxSoundSyncOnlyAdaptor : public wxSoundBackend
+{
+public:
+ wxSoundSyncOnlyAdaptor(wxSoundBackend *backend)
+ : m_backend(backend), m_playing(false) {}
+ virtual ~wxSoundSyncOnlyAdaptor()
+ {
+ delete m_backend;
+ }
+ wxString GetName() const
+ {
+ return m_backend->GetName();
+ }
+ int GetPriority() const
+ {
+ return m_backend->GetPriority();
+ }
+ bool IsAvailable() const
+ {
+ return m_backend->IsAvailable();
+ }
+ bool HasNativeAsyncPlayback() const
+ {
+ return true;
+ }
+ bool Play(wxSoundData *data, unsigned flags,
+ volatile wxSoundPlaybackStatus *status);
+ void Stop();
+ bool IsPlaying() const;
+
+private:
+ friend class wxSoundAsyncPlaybackThread;
+
+ wxSoundBackend *m_backend;
+ bool m_playing;
+#if wxUSE_THREADS
+ // player thread holds this mutex and releases it after it finishes
+ // playing, so that the main thread knows when it can play sound
+ wxMutex m_mutexRightToPlay;
+ wxSoundPlaybackStatus m_status;
+#endif
+};
+
+
+#if wxUSE_THREADS
+wxThread::ExitCode wxSoundAsyncPlaybackThread::Entry()
+{
+ m_adapt->m_backend->Play(m_data, m_flags & ~wxSOUND_ASYNC,
+ &m_adapt->m_status);
+
+ m_data->DecRef();
+ m_adapt->m_playing = false;
+ m_adapt->m_mutexRightToPlay.Unlock();
+ wxLogTrace(wxT("sound"), wxT("terminated async playback thread"));
+ return 0;
+}
+#endif
+
+bool wxSoundSyncOnlyAdaptor::Play(wxSoundData *data, unsigned flags,
+ volatile wxSoundPlaybackStatus *status)
+{
+ Stop();
+ if (flags & wxSOUND_ASYNC)
+ {
+#if wxUSE_THREADS
+ m_mutexRightToPlay.Lock();
+ m_status.m_playing = true;
+ m_status.m_stopRequested = false;
+ data->IncRef();
+ wxThread *th = new wxSoundAsyncPlaybackThread(this, data, flags);
+ th->Create();
+ th->Run();
+ wxLogTrace(wxT("sound"), wxT("launched async playback thread"));
+ return true;
+#else
+ wxLogError(_("Unable to play sound asynchronously."));
+ return false;
+#endif
+ }
+ else
+ {
+#if wxUSE_THREADS
+ m_mutexRightToPlay.Lock();
+#endif
+ bool rv = m_backend->Play(data, flags, status);
+#if wxUSE_THREADS
+ m_mutexRightToPlay.Unlock();
+#endif
+ return rv;
+ }
+}
+
+void wxSoundSyncOnlyAdaptor::Stop()
+{
+ wxLogTrace(wxT("sound"), wxT("asking audio to stop"));
+
+#if wxUSE_THREADS
+ // tell the player thread (if running) to stop playback ASAP:
+ m_status.m_stopRequested = true;
+
+ // acquire the mutex to be sure no sound is being played, then
+ // release it because we don't need it for anything (the effect of this
+ // is that calling thread will wait until playback thread reacts to
+ // our request to interrupt playback):
+ m_mutexRightToPlay.Lock();
+ m_mutexRightToPlay.Unlock();
+ wxLogTrace(wxT("sound"), wxT("audio was stopped"));
+#endif
+}
+
+bool wxSoundSyncOnlyAdaptor::IsPlaying() const
+{
+#if wxUSE_THREADS
+ return m_status.m_playing;
+#else
+ return false;
+#endif
+}
+
+
// ----------------------------------------------------------------------------
-// wxSound
+// wxSound
// ----------------------------------------------------------------------------
wxSoundBackend *wxSound::ms_backend = NULL;
Free();
}
-bool wxSound::Create(const wxString& fileName, bool isResource)
+bool wxSound::Create(const wxString& fileName,
+ bool WXUNUSED_UNLESS_DEBUG(isResource))
{
wxASSERT_MSG( !isResource,
- _T("Loading sound from resources is only supported on Windows") );
-
+ wxT("Loading sound from resources is only supported on Windows") );
+
Free();
-
+
wxFile fileWave;
if (!fileWave.Open(fileName, wxFile::read))
- {
- return false;
- }
+ {
+ return false;
+ }
- size_t len = fileWave.Length();
+ wxFileOffset lenOrig = fileWave.Length();
+ if ( lenOrig == wxInvalidOffset )
+ return false;
+
+ size_t len = wx_truncate_cast(size_t, lenOrig);
wxUint8 *data = new wxUint8[len];
- if (fileWave.Read(data, len) != len)
+ if ( fileWave.Read(data, len) != lenOrig )
{
+ delete [] data;
wxLogError(_("Couldn't load sound data from '%s'."), fileName.c_str());
return false;
}
if (!LoadWAV(data, len, false))
{
+ delete [] data;
wxLogError(_("Sound file '%s' is in unsupported format."),
fileName.c_str());
return false;
}
-
+
return true;
}
{
// FIXME -- make this fully dynamic when plugins architecture is in
// place
-#ifdef HAVE_SYS_SOUNDCARD_H
- ms_backend = new wxSoundBackendOSS();
- if (!ms_backend->IsAvailable())
- {
- wxDELETE(ms_backend);
- }
-#endif
-
#if wxUSE_LIBSDL
- if (!ms_backend)
+ //if (!ms_backend)
{
#if !wxUSE_PLUGINS
ms_backend = wxCreateSoundBackendSDL();
#else
wxString dllname;
- dllname.Printf(_T("%s/%s"),
+ dllname.Printf(wxT("%s/%s"),
wxDynamicLibrary::GetPluginsDirectory().c_str(),
wxDynamicLibrary::CanonicalizePluginName(
- _T("sound_sdl"), wxDL_PLUGIN_BASE).c_str());
- wxLogTrace(_T("sound"),
- _T("trying to load SDL plugin from '%s'..."),
+ wxT("sound_sdl"), wxDL_PLUGIN_BASE).c_str());
+ wxLogTrace(wxT("sound"),
+ wxT("trying to load SDL plugin from '%s'..."),
dllname.c_str());
wxLogNull null;
ms_backendSDL = new wxDynamicLibrary(dllname, wxDL_NOW);
}
#endif
+#ifdef HAVE_SYS_SOUNDCARD_H
+ if (!ms_backend)
+ {
+ ms_backend = new wxSoundBackendOSS();
+ if (!ms_backend->IsAvailable())
+ {
+ wxDELETE(ms_backend);
+ }
+ }
+#endif
+
if (!ms_backend)
ms_backend = new wxSoundBackendNull();
- wxLogTrace(_T("sound"),
- _T("using backend '%s'"), ms_backend->GetName().c_str());
+ if (!ms_backend->HasNativeAsyncPlayback())
+ ms_backend = new wxSoundSyncOnlyAdaptor(ms_backend);
+
+ wxLogTrace(wxT("sound"),
+ wxT("using backend '%s'"), ms_backend->GetName().c_str());
}
}
{
if (ms_backend)
{
- wxLogTrace(_T("sound"), _T("unloading backend"));
- delete ms_backend;
- ms_backend = NULL;
+ wxLogTrace(wxT("sound"), wxT("unloading backend"));
+
+ Stop();
+
+ wxDELETE(ms_backend);
#if wxUSE_LIBSDL && wxUSE_PLUGINS
delete ms_backendSDL;
#endif
}
}
-bool wxSound::DoPlay(unsigned flags)
+bool wxSound::DoPlay(unsigned flags) const
{
- wxCHECK_MSG( IsOk(), false, _T("Attempt to play invalid wave data") );
+ wxCHECK_MSG( IsOk(), false, wxT("Attempt to play invalid wave data") );
EnsureBackend();
+ wxSoundPlaybackStatus status;
+ status.m_playing = true;
+ status.m_stopRequested = false;
+ return ms_backend->Play(m_data, flags, &status);
+}
- if ((flags & wxSOUND_ASYNC) && !ms_backend->HasNativeAsyncPlayback())
- {
-#if wxUSE_THREADS
- wxMutexLocker locker(gs_soundMutex);
- m_data->IncRef();
- wxThread *th = new wxSoundAsyncPlaybackThread(ms_backend, m_data, flags);
- th->Create();
- th->Run();
- wxLogTrace(_T("sound"), _T("launched async playback thread"));
-#else
- wxLogError(_("Unable to play sound asynchronously."));
- return false;
-#endif
- }
+/*static*/ void wxSound::Stop()
+{
+ if (ms_backend)
+ ms_backend->Stop();
+}
+
+/*static*/ bool wxSound::IsPlaying()
+{
+ if (ms_backend)
+ return ms_backend->IsPlaying();
else
- {
- ms_backend->Play(m_data, flags);
- }
- return true;
+ return false;
}
void wxSound::Free()
{
-#if wxUSE_THREADS
- wxMutexLocker locker(gs_soundMutex);
-#endif
if (m_data)
m_data->DecRef();
}
typedef struct
-{
+{
wxUint32 uiSize;
wxUint16 uiFormatTag;
wxUint16 uiChannels;
wxUint32 ulAvgBytesPerSec;
wxUint16 uiBlockAlign;
wxUint16 uiBitsPerSample;
-} WAVEFORMAT;
+} WAVEFORMAT;
-#define MONO 1 // and stereo is 2 by wav format
#define WAVE_FORMAT_PCM 1
#define WAVE_INDEX 8
#define FMT_INDEX 12
bool wxSound::LoadWAV(const wxUint8 *data, size_t length, bool copyData)
{
- WAVEFORMAT waveformat;
- wxUint32 ul;
-
- if (length < 32 + sizeof(WAVEFORMAT))
+ // the simplest wave file header consists of 44 bytes:
+ //
+ // 0 "RIFF"
+ // 4 file size - 8
+ // 8 "WAVE"
+ //
+ // 12 "fmt "
+ // 16 chunk size |
+ // 20 format tag |
+ // 22 number of channels |
+ // 24 sample rate | WAVEFORMAT
+ // 28 average bytes per second |
+ // 32 bytes per frame |
+ // 34 bits per sample |
+ //
+ // 36 "data"
+ // 40 number of data bytes
+ // 44 (wave signal) data
+ //
+ // so check that we have at least as much
+ if ( length < 44 )
return false;
+ WAVEFORMAT waveformat;
memcpy(&waveformat, &data[FMT_INDEX + 4], sizeof(WAVEFORMAT));
waveformat.uiSize = wxUINT32_SWAP_ON_BE(waveformat.uiSize);
waveformat.uiFormatTag = wxUINT16_SWAP_ON_BE(waveformat.uiFormatTag);
waveformat.uiBlockAlign = wxUINT16_SWAP_ON_BE(waveformat.uiBlockAlign);
waveformat.uiBitsPerSample = wxUINT16_SWAP_ON_BE(waveformat.uiBitsPerSample);
+ // get the sound data size
+ wxUint32 ul;
+ memcpy(&ul, &data[FMT_INDEX + waveformat.uiSize + 12], 4);
+ ul = wxUINT32_SWAP_ON_BE(ul);
+
+ if ( length < ul + FMT_INDEX + waveformat.uiSize + 16 )
+ return false;
+
if (memcmp(data, "RIFF", 4) != 0)
return false;
if (memcmp(&data[WAVE_INDEX], "WAVE", 4) != 0)
return false;
if (memcmp(&data[FMT_INDEX + waveformat.uiSize + 8], "data", 4) != 0)
return false;
- memcpy(&ul,&data[FMT_INDEX + waveformat.uiSize + 12], 4);
- ul = wxUINT32_SWAP_ON_BE(ul);
-
- //WAS: if (ul + FMT_INDEX + waveformat.uiSize + 16 != length)
- if (ul + FMT_INDEX + waveformat.uiSize + 16 > length)
- return false;
-
+
if (waveformat.uiFormatTag != WAVE_FORMAT_PCM)
return false;
-
- if (waveformat.ulSamplesPerSec !=
+
+ if (waveformat.ulSamplesPerSec !=
waveformat.ulAvgBytesPerSec / waveformat.uiBlockAlign)
return false;
-
+
m_data = new wxSoundData;
m_data->m_channels = waveformat.uiChannels;
m_data->m_samplingRate = waveformat.ulSamplesPerSec;
else
m_data->m_dataWithHeader = (wxUint8*)data;
- m_data->m_data =
+ m_data->m_data =
(&m_data->m_dataWithHeader[FMT_INDEX + waveformat.uiSize + 8]);
return true;