1 /////////////////////////////////////////////////////////////////////////////
4 // Author: Marcel Rasche, Vaclav Slavik
8 // Copyright: (c) Julian Smart, Open Source Applications Foundation
9 // Licence: wxWindows licence
10 /////////////////////////////////////////////////////////////////////////////
12 #if defined(__GNUG__) && !defined(NO_GCC_PRAGMA)
13 #pragma implementation "sound.h"
14 #pragma implementation "soundbase.h"
17 // for compilers that support precompilation, includes "wx.h".
18 #include "wx/wxprec.h"
22 #if defined(__BORLANDC__)
31 #include <sys/ioctl.h>
33 #ifdef HAVE_SYS_SOUNDCARD_H
34 #include <sys/soundcard.h>
43 #include "wx/thread.h"
45 #include "wx/module.h"
47 #include "wx/dynlib.h"
51 // mutex for all wxSound's synchronization
52 static wxMutex gs_soundMutex
;
55 // ----------------------------------------------------------------------------
57 // ----------------------------------------------------------------------------
59 void wxSoundData::IncRef()
62 wxMutexLocker
locker(gs_soundMutex
);
67 void wxSoundData::DecRef()
70 wxMutexLocker
locker(gs_soundMutex
);
76 wxSoundData::~wxSoundData()
78 delete[] m_dataWithHeader
;
82 // ----------------------------------------------------------------------------
83 // wxSoundBackendNull, used in absence of audio API or card
84 // ----------------------------------------------------------------------------
86 class wxSoundBackendNull
: public wxSoundBackend
89 wxString
GetName() const { return _("No sound"); }
90 int GetPriority() const { return 0; }
91 bool IsAvailable() const { return true; }
92 bool HasNativeAsyncPlayback() const { return true; }
93 bool Play(wxSoundData
*WXUNUSED(data
), unsigned WXUNUSED(flags
),
94 volatile wxSoundPlaybackStatus
*WXUNUSED(status
))
97 bool IsPlaying() const { return false; }
101 // ----------------------------------------------------------------------------
102 // wxSoundBackendOSS, for Linux
103 // ----------------------------------------------------------------------------
105 #ifdef HAVE_SYS_SOUNDCARD_H
108 #define AUDIODEV "/dev/dsp" // Default path for audio device
111 class wxSoundBackendOSS
: public wxSoundBackend
114 wxString
GetName() const { return _T("Open Sound System"); }
115 int GetPriority() const { return 10; }
116 bool IsAvailable() const;
117 bool HasNativeAsyncPlayback() const { return false; }
118 bool Play(wxSoundData
*data
, unsigned flags
,
119 volatile wxSoundPlaybackStatus
*status
);
121 bool IsPlaying() const { return false; }
124 int OpenDSP(const wxSoundData
*data
);
125 bool InitDSP(int dev
, const wxSoundData
*data
);
127 int m_DSPblkSize
; // Size of the DSP buffer
128 bool m_needConversion
;
131 bool wxSoundBackendOSS::IsAvailable() const
134 fd
= open(AUDIODEV
, O_WRONLY
| O_NONBLOCK
);
141 bool wxSoundBackendOSS::Play(wxSoundData
*data
, unsigned flags
,
142 volatile wxSoundPlaybackStatus
*status
)
144 int dev
= OpenDSP(data
);
149 ioctl(dev
, SNDCTL_DSP_SYNC
, 0);
156 size_t datasize
= data
->m_dataBytes
;
160 if (status
->m_stopRequested
)
162 wxLogTrace(_T("sound"), _T("playback stopped"));
167 i
= (int)((l
+ m_DSPblkSize
) < datasize
?
168 m_DSPblkSize
: (datasize
- l
));
169 if (write(dev
, &data
->m_data
[l
], i
) != i
)
174 } while (play
&& l
< datasize
);
175 } while (flags
& wxSOUND_LOOP
);
181 int wxSoundBackendOSS::OpenDSP(const wxSoundData
*data
)
185 if ((dev
= open(AUDIODEV
, O_WRONLY
, 0)) <0)
188 if (!InitDSP(dev
, data
) || m_needConversion
)
198 bool wxSoundBackendOSS::InitDSP(int dev
, const wxSoundData
*data
)
203 if (ioctl(dev
, SNDCTL_DSP_RESET
, 0) < 0)
205 wxLogTrace(_T("sound"), _T("unable to reset dsp"));
209 m_needConversion
= false;
211 tmp
= data
->m_bitsPerSample
;
212 if (ioctl(dev
, SNDCTL_DSP_SAMPLESIZE
, &tmp
) < 0)
214 wxLogTrace(_T("sound"), _T("IOCTL failure (SNDCTL_DSP_SAMPLESIZE)"));
217 if (tmp
!= data
->m_bitsPerSample
)
219 wxLogTrace(_T("sound"),
220 _T("Unable to set DSP sample size to %d (wants %d)"),
221 data
->m_bitsPerSample
, tmp
);
222 m_needConversion
= true;
225 unsigned stereo
= data
->m_channels
== 1 ? 0 : 1;
227 if (ioctl(dev
, SNDCTL_DSP_STEREO
, &tmp
) < 0)
229 wxLogTrace(_T("sound"), _T("IOCTL failure (SNDCTL_DSP_STEREO)"));
234 wxLogTrace(_T("sound"), _T("Unable to set DSP to %s."), stereo
? _T("stereo"):_T("mono"));
235 m_needConversion
= true;
238 tmp
= data
->m_samplingRate
;
239 if (ioctl(dev
, SNDCTL_DSP_SPEED
, &tmp
) < 0)
241 wxLogTrace(_T("sound"), _T("IOCTL failure (SNDCTL_DSP_SPEED)"));
244 if (tmp
!= data
->m_samplingRate
)
246 // If the rate the sound card is using is not within 1% of what the
247 // data specified then override the data setting. The only reason not
248 // to always override this is because of clock-rounding
249 // problems. Sound cards will sometimes use things like 44101 when you
250 // ask for 44100. No need overriding this and having strange output
251 // file rates for something that we can't hear anyways.
252 if (data
->m_samplingRate
- tmp
> (tmp
* .01) ||
253 tmp
- data
->m_samplingRate
> (tmp
* .01)) {
254 wxLogTrace(_T("sound"),
255 _T("Unable to set DSP sampling rate to %d (wants %d)"),
256 data
->m_samplingRate
, tmp
);
257 m_needConversion
= true;
261 // Do this last because some drivers can adjust the buffer sized based on
262 // the sampling rate, etc.
263 if (ioctl(dev
, SNDCTL_DSP_GETBLKSIZE
, &m_DSPblkSize
) < 0)
265 wxLogTrace(_T("sound"), _T("IOCTL failure (SNDCTL_DSP_GETBLKSIZE)"));
271 #endif // HAVE_SYS_SOUNDCARD_H
273 // ----------------------------------------------------------------------------
274 // wxSoundSyncOnlyAdaptor
275 // ----------------------------------------------------------------------------
279 class wxSoundSyncOnlyAdaptor
;
281 // this class manages asynchronous playback of audio if the backend doesn't
282 // support it natively (e.g. OSS backend)
283 class wxSoundAsyncPlaybackThread
: public wxThread
286 wxSoundAsyncPlaybackThread(wxSoundSyncOnlyAdaptor
*adaptor
,
287 wxSoundData
*data
, unsigned flags
)
288 : wxThread(), m_adapt(adaptor
), m_data(data
), m_flags(flags
) {}
289 virtual ExitCode
Entry();
292 wxSoundSyncOnlyAdaptor
*m_adapt
;
297 #endif // wxUSE_THREADS
299 // This class turns wxSoundBackend that doesn't support asynchronous playback
300 // into one that does
301 class wxSoundSyncOnlyAdaptor
: public wxSoundBackend
304 wxSoundSyncOnlyAdaptor(wxSoundBackend
*backend
)
305 : m_backend(backend
), m_playing(false) {}
306 ~wxSoundSyncOnlyAdaptor()
310 wxString
GetName() const
312 return m_backend
->GetName();
314 int GetPriority() const
316 return m_backend
->GetPriority();
318 bool IsAvailable() const
320 return m_backend
->IsAvailable();
322 bool HasNativeAsyncPlayback() const
326 bool Play(wxSoundData
*data
, unsigned flags
,
327 volatile wxSoundPlaybackStatus
*status
);
329 bool IsPlaying() const;
332 friend class wxSoundAsyncPlaybackThread
;
334 wxSoundBackend
*m_backend
;
337 // player thread holds this mutex and releases it after it finishes
338 // playing, so that the main thread knows when it can play sound
339 wxMutex m_mutexRightToPlay
;
340 wxSoundPlaybackStatus m_status
;
346 wxThread::ExitCode
wxSoundAsyncPlaybackThread::Entry()
348 m_adapt
->m_backend
->Play(m_data
, m_flags
& ~wxSOUND_ASYNC
,
352 m_adapt
->m_playing
= false;
353 m_adapt
->m_mutexRightToPlay
.Unlock();
354 wxLogTrace(_T("sound"), _T("terminated async playback thread"));
359 bool wxSoundSyncOnlyAdaptor::Play(wxSoundData
*data
, unsigned flags
,
360 volatile wxSoundPlaybackStatus
*status
)
363 if (flags
& wxSOUND_ASYNC
)
366 m_mutexRightToPlay
.Lock();
367 m_status
.m_playing
= true;
368 m_status
.m_stopRequested
= false;
370 wxThread
*th
= new wxSoundAsyncPlaybackThread(this, data
, flags
);
373 wxLogTrace(_T("sound"), _T("launched async playback thread"));
376 wxLogError(_("Unable to play sound asynchronously."));
383 m_mutexRightToPlay
.Lock();
385 bool rv
= m_backend
->Play(data
, flags
, status
);
387 m_mutexRightToPlay
.Unlock();
393 void wxSoundSyncOnlyAdaptor::Stop()
395 wxLogTrace(_T("sound"), _T("asking audio to stop"));
398 // tell the player thread (if running) to stop playback ASAP:
399 m_status
.m_stopRequested
= true;
401 // acquire the mutex to be sure no sound is being played, then
402 // release it because we don't need it for anything (the effect of this
403 // is that calling thread will wait until playback thread reacts to
404 // our request to interrupt playback):
405 m_mutexRightToPlay
.Lock();
406 m_mutexRightToPlay
.Unlock();
407 wxLogTrace(_T("sound"), _T("audio was stopped"));
411 bool wxSoundSyncOnlyAdaptor::IsPlaying() const
414 return m_status
.m_playing
;
421 // ----------------------------------------------------------------------------
423 // ----------------------------------------------------------------------------
425 wxSoundBackend
*wxSound::ms_backend
= NULL
;
427 // FIXME - temporary, until we have plugins architecture
430 wxDynamicLibrary
*wxSound::ms_backendSDL
= NULL
;
432 extern "C" wxSoundBackend
*wxCreateSoundBackendSDL();
436 wxSound::wxSound() : m_data(NULL
)
440 wxSound::wxSound(const wxString
& sFileName
, bool isResource
) : m_data(NULL
)
442 Create(sFileName
, isResource
);
445 wxSound::wxSound(int size
, const wxByte
* data
) : m_data(NULL
)
455 bool wxSound::Create(const wxString
& fileName
, bool isResource
)
457 wxASSERT_MSG( !isResource
,
458 _T("Loading sound from resources is only supported on Windows") );
463 if (!fileWave
.Open(fileName
, wxFile::read
))
468 wxFileOffset len
= fileWave
.Length();
469 wxUint8
*data
= new wxUint8
[len
];
470 if (fileWave
.Read(data
, len
) != len
)
472 wxLogError(_("Couldn't load sound data from '%s'."), fileName
.c_str());
476 if (!LoadWAV(data
, len
, false))
478 wxLogError(_("Sound file '%s' is in unsupported format."),
486 bool wxSound::Create(int size
, const wxByte
* data
)
488 wxASSERT( data
!= NULL
);
491 if (!LoadWAV(data
, size
, true))
493 wxLogError(_("Sound data are in unsupported format."));
499 /*static*/ void wxSound::EnsureBackend()
503 // FIXME -- make this fully dynamic when plugins architecture is in
509 ms_backend
= wxCreateSoundBackendSDL();
512 dllname
.Printf(_T("%s/%s"),
513 wxDynamicLibrary::GetPluginsDirectory().c_str(),
514 wxDynamicLibrary::CanonicalizePluginName(
515 _T("sound_sdl"), wxDL_PLUGIN_BASE
).c_str());
516 wxLogTrace(_T("sound"),
517 _T("trying to load SDL plugin from '%s'..."),
520 ms_backendSDL
= new wxDynamicLibrary(dllname
, wxDL_NOW
);
521 if (!ms_backendSDL
->IsLoaded())
523 wxDELETE(ms_backendSDL
);
527 typedef wxSoundBackend
*(*wxCreateSoundBackend_t
)();
528 wxDYNLIB_FUNCTION(wxCreateSoundBackend_t
,
529 wxCreateSoundBackendSDL
, *ms_backendSDL
);
530 if (pfnwxCreateSoundBackendSDL
)
532 ms_backend
= (*pfnwxCreateSoundBackendSDL
)();
536 if (ms_backend
&& !ms_backend
->IsAvailable())
538 wxDELETE(ms_backend
);
543 #ifdef HAVE_SYS_SOUNDCARD_H
546 ms_backend
= new wxSoundBackendOSS();
547 if (!ms_backend
->IsAvailable())
549 wxDELETE(ms_backend
);
555 ms_backend
= new wxSoundBackendNull();
557 if (!ms_backend
->HasNativeAsyncPlayback())
558 ms_backend
= new wxSoundSyncOnlyAdaptor(ms_backend
);
560 wxLogTrace(_T("sound"),
561 _T("using backend '%s'"), ms_backend
->GetName().c_str());
565 /*static*/ void wxSound::UnloadBackend()
569 wxLogTrace(_T("sound"), _T("unloading backend"));
575 #if wxUSE_LIBSDL && wxUSE_PLUGINS
576 delete ms_backendSDL
;
581 bool wxSound::DoPlay(unsigned flags
) const
583 wxCHECK_MSG( IsOk(), false, _T("Attempt to play invalid wave data") );
586 wxSoundPlaybackStatus status
;
587 status
.m_playing
= true;
588 status
.m_stopRequested
= false;
589 return ms_backend
->Play(m_data
, flags
, &status
);
592 /*static*/ void wxSound::Stop()
598 /*static*/ bool wxSound::IsPlaying()
601 return ms_backend
->IsPlaying();
615 wxUint16 uiFormatTag
;
617 wxUint32 ulSamplesPerSec
;
618 wxUint32 ulAvgBytesPerSec
;
619 wxUint16 uiBlockAlign
;
620 wxUint16 uiBitsPerSample
;
623 #define WAVE_FORMAT_PCM 1
627 bool wxSound::LoadWAV(const wxUint8
*data
, size_t length
, bool copyData
)
629 WAVEFORMAT waveformat
;
632 if (length
< 32 + sizeof(WAVEFORMAT
))
635 memcpy(&waveformat
, &data
[FMT_INDEX
+ 4], sizeof(WAVEFORMAT
));
636 waveformat
.uiSize
= wxUINT32_SWAP_ON_BE(waveformat
.uiSize
);
637 waveformat
.uiFormatTag
= wxUINT16_SWAP_ON_BE(waveformat
.uiFormatTag
);
638 waveformat
.uiChannels
= wxUINT16_SWAP_ON_BE(waveformat
.uiChannels
);
639 waveformat
.ulSamplesPerSec
= wxUINT32_SWAP_ON_BE(waveformat
.ulSamplesPerSec
);
640 waveformat
.ulAvgBytesPerSec
= wxUINT32_SWAP_ON_BE(waveformat
.ulAvgBytesPerSec
);
641 waveformat
.uiBlockAlign
= wxUINT16_SWAP_ON_BE(waveformat
.uiBlockAlign
);
642 waveformat
.uiBitsPerSample
= wxUINT16_SWAP_ON_BE(waveformat
.uiBitsPerSample
);
644 if (memcmp(data
, "RIFF", 4) != 0)
646 if (memcmp(&data
[WAVE_INDEX
], "WAVE", 4) != 0)
648 if (memcmp(&data
[FMT_INDEX
], "fmt ", 4) != 0)
650 if (memcmp(&data
[FMT_INDEX
+ waveformat
.uiSize
+ 8], "data", 4) != 0)
652 memcpy(&ul
,&data
[FMT_INDEX
+ waveformat
.uiSize
+ 12], 4);
653 ul
= wxUINT32_SWAP_ON_BE(ul
);
655 //WAS: if (ul + FMT_INDEX + waveformat.uiSize + 16 != length)
656 if (ul
+ FMT_INDEX
+ waveformat
.uiSize
+ 16 > length
)
659 if (waveformat
.uiFormatTag
!= WAVE_FORMAT_PCM
)
662 if (waveformat
.ulSamplesPerSec
!=
663 waveformat
.ulAvgBytesPerSec
/ waveformat
.uiBlockAlign
)
666 m_data
= new wxSoundData
;
667 m_data
->m_channels
= waveformat
.uiChannels
;
668 m_data
->m_samplingRate
= waveformat
.ulSamplesPerSec
;
669 m_data
->m_bitsPerSample
= waveformat
.uiBitsPerSample
;
670 m_data
->m_samples
= ul
/ (m_data
->m_channels
* m_data
->m_bitsPerSample
/ 8);
671 m_data
->m_dataBytes
= ul
;
675 m_data
->m_dataWithHeader
= new wxUint8
[length
];
676 memcpy(m_data
->m_dataWithHeader
, data
, length
);
679 m_data
->m_dataWithHeader
= (wxUint8
*)data
;
682 (&m_data
->m_dataWithHeader
[FMT_INDEX
+ waveformat
.uiSize
+ 8]);
688 // ----------------------------------------------------------------------------
689 // wxSoundCleanupModule
690 // ----------------------------------------------------------------------------
692 class wxSoundCleanupModule
: public wxModule
695 bool OnInit() { return true; }
696 void OnExit() { wxSound::UnloadBackend(); }
697 DECLARE_DYNAMIC_CLASS(wxSoundCleanupModule
)
700 IMPLEMENT_DYNAMIC_CLASS(wxSoundCleanupModule
, wxModule
)