]> git.saurik.com Git - wxWidgets.git/blobdiff - src/unix/sound.cpp
fix memory leak while testing for correct Clone() implementation (closes #10304)
[wxWidgets.git] / src / unix / sound.cpp
index b15498aeba7e6835a53cdd6904ad9cae4254b2ff..0afab5a47be20af4ea990f0dc8ad1a8f6ab5179c 100644 (file)
@@ -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 <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
 // ----------------------------------------------------------------------------
@@ -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;