X-Git-Url: https://git.saurik.com/wxWidgets.git/blobdiff_plain/9be32e8f2d2b7ada287f03a0e19e3a40db2e9770..f7ae43f080576ef142d3884073df298294860328:/src/unix/sound.cpp diff --git a/src/unix/sound.cpp b/src/unix/sound.cpp index b15498aeba..0afab5a47b 100644 --- a/src/unix/sound.cpp +++ b/src/unix/sound.cpp @@ -1,36 +1,29 @@ ///////////////////////////////////////////////////////////////////////////// -// 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 #include #include #include -#if HAVE_SYS_SOUNDCARD_H +#ifdef HAVE_SYS_SOUNDCARD_H #include #endif @@ -38,14 +31,47 @@ #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 // ---------------------------------------------------------------------------- @@ -57,8 +83,11 @@ public: 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; } }; @@ -79,14 +108,17 @@ public: 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 @@ -99,15 +131,16 @@ 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; @@ -117,8 +150,15 @@ bool wxSoundBackendOSS::Play(wxSoundData *data, unsigned flags) do { + if (status->m_stopRequested) + { + wxLogTrace(_T("sound"), _T("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; @@ -134,14 +174,11 @@ bool wxSoundBackendOSS::Play(wxSoundData *data, unsigned flags) 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; @@ -150,82 +187,232 @@ int wxSoundBackendOSS::OpenDSP(const wxSoundData *data) 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(_T("sound"), _T("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(_T("sound"), _T("IOCTL failure (SNDCTL_DSP_SAMPLESIZE)")); + return false; + } + if (tmp != data->m_bitsPerSample) + { + wxLogTrace(_T("sound"), + _T("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(_T("sound"), _T("IOCTL failure (SNDCTL_DSP_STEREO)")); + return false; + } + if (tmp != stereo) + { + wxLogTrace(_T("sound"), _T("Unable to set DSP to %s."), stereo? _T("stereo"):_T("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(_T("sound"), _T("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(_T("sound"), + _T("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(_T("sound"), _T("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(_T("sound"), _T("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(_T("sound"), _T("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(_T("sound"), _T("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(_T("sound"), _T("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; @@ -258,34 +445,41 @@ wxSound::~wxSound() 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") ); - + 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; } @@ -308,16 +502,8 @@ bool wxSound::Create(int size, const wxByte* data) { // 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(); @@ -354,9 +540,23 @@ bool wxSound::Create(int size, const wxByte* data) } #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(); + if (!ms_backend->HasNativeAsyncPlayback()) + ms_backend = new wxSoundSyncOnlyAdaptor(ms_backend); + wxLogTrace(_T("sound"), _T("using backend '%s'"), ms_backend->GetName().c_str()); } @@ -367,6 +567,9 @@ bool wxSound::Create(int size, const wxByte* data) if (ms_backend) { wxLogTrace(_T("sound"), _T("unloading backend")); + + Stop(); + delete ms_backend; ms_backend = NULL; #if wxUSE_LIBSDL && wxUSE_PLUGINS @@ -375,44 +578,39 @@ bool wxSound::Create(int size, const wxByte* data) } } -bool wxSound::DoPlay(unsigned flags) +bool wxSound::DoPlay(unsigned flags) const { wxCHECK_MSG( IsOk(), false, _T("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; @@ -420,21 +618,38 @@ typedef struct 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); @@ -444,6 +659,14 @@ bool wxSound::LoadWAV(const wxUint8 *data, size_t length, bool copyData) 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) @@ -452,20 +675,14 @@ bool wxSound::LoadWAV(const wxUint8 *data, size_t length, bool copyData) 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; @@ -481,7 +698,7 @@ bool wxSound::LoadWAV(const wxUint8 *data, size_t length, bool copyData) 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;