X-Git-Url: https://git.saurik.com/wxWidgets.git/blobdiff_plain/9be32e8f2d2b7ada287f03a0e19e3a40db2e9770..90ff87d7b01e32cbfa856b41e11b646316c2e88a:/src/unix/sound.cpp diff --git a/src/unix/sound.cpp b/src/unix/sound.cpp index b15498aeba..1fb4999964 100644 --- a/src/unix/sound.cpp +++ b/src/unix/sound.cpp @@ -5,7 +5,7 @@ // Modified by: // Created: 25/10/98 // RCS-ID: $Id$ -// Copyright: (c) Julian Smart, Vaclav Slavik +// Copyright: (c) Julian Smart, Open Source Applications Foundation // Licence: wxWindows licence ///////////////////////////////////////////////////////////////////////////// @@ -23,14 +23,14 @@ #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 @@ -46,6 +46,39 @@ #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 +90,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 +115,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,7 +138,8 @@ 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); @@ -107,7 +147,7 @@ bool wxSoundBackendOSS::Play(wxSoundData *data, unsigned flags) return false; ioctl(dev, SNDCTL_DSP_SYNC, 0); - + do { bool play = true; @@ -117,8 +157,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; @@ -126,7 +173,7 @@ bool wxSoundBackendOSS::Play(wxSoundData *data, unsigned flags) l += i; } while (play && l < datasize); } while (flags & wxSOUND_LOOP); - + close(dev); return true; } @@ -138,10 +185,7 @@ int wxSoundBackendOSS::OpenDSP(const wxSoundData *data) 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,80 +194,230 @@ 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) + unsigned tmp; + + // Reset the dsp + if (ioctl(dev, SNDCTL_DSP_RESET, 0) < 0) + { + wxLogTrace(_T("sound"), _T("unable to reset dsp")); return false; - if (ioctl(dev, SNDCTL_DSP_SAMPLESIZE, &iDataBits) < 0) + } + + 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 (ioctl(dev, SNDCTL_DSP_STEREO, &iChannel) < 0) + } + 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; + } + + 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 (ioctl(dev, SNDCTL_DSP_SPEED, &ulSamplingRate) < 0) + } + if (tmp != stereo) + { + wxLogTrace(_T("sound"), _T("Unable to set DSP to %s."), stereo? _T("stereo"):_T("mono")); + m_needConversion = true; + } + + 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; + } + } + + // 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 - // ---------------------------------------------------------------------------- -// wxSoundData -// ---------------------------------------------------------------------------- - -void wxSoundData::IncRef() -{ - m_refCnt++; -} - -void wxSoundData::DecRef() -{ - if (--m_refCnt == 0) - delete this; -} - -wxSoundData::~wxSoundData() -{ - delete[] m_dataWithHeader; -} - - -// ---------------------------------------------------------------------------- -// 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) {} + ~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 // ---------------------------------------------------------------------------- @@ -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,38 +578,33 @@ 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(); } @@ -420,7 +618,7 @@ 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