]> git.saurik.com Git - wxWidgets.git/blob - src/unix/sound.cpp
Ensure that validators work even in presence of pushed event handlers.
[wxWidgets.git] / src / unix / sound.cpp
1 /////////////////////////////////////////////////////////////////////////////
2 // Name: src/unix/sound.cpp
3 // Purpose: wxSound
4 // Author: Marcel Rasche, Vaclav Slavik
5 // Modified by:
6 // Created: 25/10/98
7 // RCS-ID: $Id$
8 // Copyright: (c) Julian Smart, Open Source Applications Foundation
9 // Licence: wxWindows licence
10 /////////////////////////////////////////////////////////////////////////////
11
12 // for compilers that support precompilation, includes "wx.h".
13 #include "wx/wxprec.h"
14
15 #if defined(__BORLANDC__)
16 #pragma hdrstop
17 #endif
18
19 #if wxUSE_SOUND
20
21 #include <stdio.h>
22 #include <unistd.h>
23 #include <fcntl.h>
24 #include <sys/ioctl.h>
25
26 #ifdef HAVE_SYS_SOUNDCARD_H
27 #include <sys/soundcard.h>
28 #endif
29
30 #ifndef WX_PRECOMP
31 #include "wx/event.h"
32 #include "wx/intl.h"
33 #include "wx/log.h"
34 #include "wx/module.h"
35 #endif
36
37 #include "wx/thread.h"
38 #include "wx/file.h"
39 #include "wx/sound.h"
40 #include "wx/dynlib.h"
41
42
43 #if wxUSE_THREADS
44 // mutex for all wxSound's synchronization
45 static wxMutex gs_soundMutex;
46 #endif
47
48 // ----------------------------------------------------------------------------
49 // wxSoundData
50 // ----------------------------------------------------------------------------
51
52 void wxSoundData::IncRef()
53 {
54 #if wxUSE_THREADS
55 wxMutexLocker locker(gs_soundMutex);
56 #endif
57 m_refCnt++;
58 }
59
60 void wxSoundData::DecRef()
61 {
62 #if wxUSE_THREADS
63 wxMutexLocker locker(gs_soundMutex);
64 #endif
65 if (--m_refCnt == 0)
66 delete this;
67 }
68
69 wxSoundData::~wxSoundData()
70 {
71 delete[] m_dataWithHeader;
72 }
73
74
75 // ----------------------------------------------------------------------------
76 // wxSoundBackendNull, used in absence of audio API or card
77 // ----------------------------------------------------------------------------
78
79 class wxSoundBackendNull : public wxSoundBackend
80 {
81 public:
82 wxString GetName() const { return _("No sound"); }
83 int GetPriority() const { return 0; }
84 bool IsAvailable() const { return true; }
85 bool HasNativeAsyncPlayback() const { return true; }
86 bool Play(wxSoundData *WXUNUSED(data), unsigned WXUNUSED(flags),
87 volatile wxSoundPlaybackStatus *WXUNUSED(status))
88 { return true; }
89 void Stop() {}
90 bool IsPlaying() const { return false; }
91 };
92
93
94 // ----------------------------------------------------------------------------
95 // wxSoundBackendOSS, for Linux
96 // ----------------------------------------------------------------------------
97
98 #ifdef HAVE_SYS_SOUNDCARD_H
99
100 #ifndef AUDIODEV
101 #define AUDIODEV "/dev/dsp" // Default path for audio device
102 #endif
103
104 class wxSoundBackendOSS : public wxSoundBackend
105 {
106 public:
107 wxString GetName() const { return wxT("Open Sound System"); }
108 int GetPriority() const { return 10; }
109 bool IsAvailable() const;
110 bool HasNativeAsyncPlayback() const { return false; }
111 bool Play(wxSoundData *data, unsigned flags,
112 volatile wxSoundPlaybackStatus *status);
113 void Stop() {}
114 bool IsPlaying() const { return false; }
115
116 private:
117 int OpenDSP(const wxSoundData *data);
118 bool InitDSP(int dev, const wxSoundData *data);
119
120 int m_DSPblkSize; // Size of the DSP buffer
121 bool m_needConversion;
122 };
123
124 bool wxSoundBackendOSS::IsAvailable() const
125 {
126 int fd;
127 fd = open(AUDIODEV, O_WRONLY | O_NONBLOCK);
128 if (fd < 0)
129 return false;
130 close(fd);
131 return true;
132 }
133
134 bool wxSoundBackendOSS::Play(wxSoundData *data, unsigned flags,
135 volatile wxSoundPlaybackStatus *status)
136 {
137 int dev = OpenDSP(data);
138
139 if (dev < 0)
140 return false;
141
142 ioctl(dev, SNDCTL_DSP_SYNC, 0);
143
144 do
145 {
146 bool play = true;
147 int i;
148 unsigned l = 0;
149 size_t datasize = data->m_dataBytes;
150
151 do
152 {
153 if (status->m_stopRequested)
154 {
155 wxLogTrace(wxT("sound"), wxT("playback stopped"));
156 close(dev);
157 return true;
158 }
159
160 i= (int)((l + m_DSPblkSize) < datasize ?
161 m_DSPblkSize : (datasize - l));
162 if (write(dev, &data->m_data[l], i) != i)
163 {
164 play = false;
165 }
166 l += i;
167 } while (play && l < datasize);
168 } while (flags & wxSOUND_LOOP);
169
170 close(dev);
171 return true;
172 }
173
174 int wxSoundBackendOSS::OpenDSP(const wxSoundData *data)
175 {
176 int dev = -1;
177
178 if ((dev = open(AUDIODEV, O_WRONLY, 0)) <0)
179 return -1;
180
181 if (!InitDSP(dev, data) || m_needConversion)
182 {
183 close(dev);
184 return -1;
185 }
186
187 return dev;
188 }
189
190
191 bool wxSoundBackendOSS::InitDSP(int dev, const wxSoundData *data)
192 {
193 unsigned tmp;
194
195 // Reset the dsp
196 if (ioctl(dev, SNDCTL_DSP_RESET, 0) < 0)
197 {
198 wxLogTrace(wxT("sound"), wxT("unable to reset dsp"));
199 return false;
200 }
201
202 m_needConversion = false;
203
204 tmp = data->m_bitsPerSample;
205 if (ioctl(dev, SNDCTL_DSP_SAMPLESIZE, &tmp) < 0)
206 {
207 wxLogTrace(wxT("sound"), wxT("IOCTL failure (SNDCTL_DSP_SAMPLESIZE)"));
208 return false;
209 }
210 if (tmp != data->m_bitsPerSample)
211 {
212 wxLogTrace(wxT("sound"),
213 wxT("Unable to set DSP sample size to %d (wants %d)"),
214 data->m_bitsPerSample, tmp);
215 m_needConversion = true;
216 }
217
218 unsigned stereo = data->m_channels == 1 ? 0 : 1;
219 tmp = stereo;
220 if (ioctl(dev, SNDCTL_DSP_STEREO, &tmp) < 0)
221 {
222 wxLogTrace(wxT("sound"), wxT("IOCTL failure (SNDCTL_DSP_STEREO)"));
223 return false;
224 }
225 if (tmp != stereo)
226 {
227 wxLogTrace(wxT("sound"), wxT("Unable to set DSP to %s."), stereo? wxT("stereo"):wxT("mono"));
228 m_needConversion = true;
229 }
230
231 tmp = data->m_samplingRate;
232 if (ioctl(dev, SNDCTL_DSP_SPEED, &tmp) < 0)
233 {
234 wxLogTrace(wxT("sound"), wxT("IOCTL failure (SNDCTL_DSP_SPEED)"));
235 return false;
236 }
237 if (tmp != data->m_samplingRate)
238 {
239 // If the rate the sound card is using is not within 1% of what the
240 // data specified then override the data setting. The only reason not
241 // to always override this is because of clock-rounding
242 // problems. Sound cards will sometimes use things like 44101 when you
243 // ask for 44100. No need overriding this and having strange output
244 // file rates for something that we can't hear anyways.
245 if (data->m_samplingRate - tmp > (tmp * .01) ||
246 tmp - data->m_samplingRate > (tmp * .01)) {
247 wxLogTrace(wxT("sound"),
248 wxT("Unable to set DSP sampling rate to %d (wants %d)"),
249 data->m_samplingRate, tmp);
250 m_needConversion = true;
251 }
252 }
253
254 // Do this last because some drivers can adjust the buffer sized based on
255 // the sampling rate, etc.
256 if (ioctl(dev, SNDCTL_DSP_GETBLKSIZE, &m_DSPblkSize) < 0)
257 {
258 wxLogTrace(wxT("sound"), wxT("IOCTL failure (SNDCTL_DSP_GETBLKSIZE)"));
259 return false;
260 }
261 return true;
262 }
263
264 #endif // HAVE_SYS_SOUNDCARD_H
265
266 // ----------------------------------------------------------------------------
267 // wxSoundSyncOnlyAdaptor
268 // ----------------------------------------------------------------------------
269
270 #if wxUSE_THREADS
271
272 class wxSoundSyncOnlyAdaptor;
273
274 // this class manages asynchronous playback of audio if the backend doesn't
275 // support it natively (e.g. OSS backend)
276 class wxSoundAsyncPlaybackThread : public wxThread
277 {
278 public:
279 wxSoundAsyncPlaybackThread(wxSoundSyncOnlyAdaptor *adaptor,
280 wxSoundData *data, unsigned flags)
281 : wxThread(), m_adapt(adaptor), m_data(data), m_flags(flags) {}
282 virtual ExitCode Entry();
283
284 protected:
285 wxSoundSyncOnlyAdaptor *m_adapt;
286 wxSoundData *m_data;
287 unsigned m_flags;
288 };
289
290 #endif // wxUSE_THREADS
291
292 // This class turns wxSoundBackend that doesn't support asynchronous playback
293 // into one that does
294 class wxSoundSyncOnlyAdaptor : public wxSoundBackend
295 {
296 public:
297 wxSoundSyncOnlyAdaptor(wxSoundBackend *backend)
298 : m_backend(backend), m_playing(false) {}
299 virtual ~wxSoundSyncOnlyAdaptor()
300 {
301 delete m_backend;
302 }
303 wxString GetName() const
304 {
305 return m_backend->GetName();
306 }
307 int GetPriority() const
308 {
309 return m_backend->GetPriority();
310 }
311 bool IsAvailable() const
312 {
313 return m_backend->IsAvailable();
314 }
315 bool HasNativeAsyncPlayback() const
316 {
317 return true;
318 }
319 bool Play(wxSoundData *data, unsigned flags,
320 volatile wxSoundPlaybackStatus *status);
321 void Stop();
322 bool IsPlaying() const;
323
324 private:
325 friend class wxSoundAsyncPlaybackThread;
326
327 wxSoundBackend *m_backend;
328 bool m_playing;
329 #if wxUSE_THREADS
330 // player thread holds this mutex and releases it after it finishes
331 // playing, so that the main thread knows when it can play sound
332 wxMutex m_mutexRightToPlay;
333 wxSoundPlaybackStatus m_status;
334 #endif
335 };
336
337
338 #if wxUSE_THREADS
339 wxThread::ExitCode wxSoundAsyncPlaybackThread::Entry()
340 {
341 m_adapt->m_backend->Play(m_data, m_flags & ~wxSOUND_ASYNC,
342 &m_adapt->m_status);
343
344 m_data->DecRef();
345 m_adapt->m_playing = false;
346 m_adapt->m_mutexRightToPlay.Unlock();
347 wxLogTrace(wxT("sound"), wxT("terminated async playback thread"));
348 return 0;
349 }
350 #endif
351
352 bool wxSoundSyncOnlyAdaptor::Play(wxSoundData *data, unsigned flags,
353 volatile wxSoundPlaybackStatus *status)
354 {
355 Stop();
356 if (flags & wxSOUND_ASYNC)
357 {
358 #if wxUSE_THREADS
359 m_mutexRightToPlay.Lock();
360 m_status.m_playing = true;
361 m_status.m_stopRequested = false;
362 data->IncRef();
363 wxThread *th = new wxSoundAsyncPlaybackThread(this, data, flags);
364 th->Create();
365 th->Run();
366 wxLogTrace(wxT("sound"), wxT("launched async playback thread"));
367 return true;
368 #else
369 wxLogError(_("Unable to play sound asynchronously."));
370 return false;
371 #endif
372 }
373 else
374 {
375 #if wxUSE_THREADS
376 m_mutexRightToPlay.Lock();
377 #endif
378 bool rv = m_backend->Play(data, flags, status);
379 #if wxUSE_THREADS
380 m_mutexRightToPlay.Unlock();
381 #endif
382 return rv;
383 }
384 }
385
386 void wxSoundSyncOnlyAdaptor::Stop()
387 {
388 wxLogTrace(wxT("sound"), wxT("asking audio to stop"));
389
390 #if wxUSE_THREADS
391 // tell the player thread (if running) to stop playback ASAP:
392 m_status.m_stopRequested = true;
393
394 // acquire the mutex to be sure no sound is being played, then
395 // release it because we don't need it for anything (the effect of this
396 // is that calling thread will wait until playback thread reacts to
397 // our request to interrupt playback):
398 m_mutexRightToPlay.Lock();
399 m_mutexRightToPlay.Unlock();
400 wxLogTrace(wxT("sound"), wxT("audio was stopped"));
401 #endif
402 }
403
404 bool wxSoundSyncOnlyAdaptor::IsPlaying() const
405 {
406 #if wxUSE_THREADS
407 return m_status.m_playing;
408 #else
409 return false;
410 #endif
411 }
412
413
414 // ----------------------------------------------------------------------------
415 // wxSound
416 // ----------------------------------------------------------------------------
417
418 wxSoundBackend *wxSound::ms_backend = NULL;
419
420 // FIXME - temporary, until we have plugins architecture
421 #if wxUSE_LIBSDL
422 #if wxUSE_PLUGINS
423 wxDynamicLibrary *wxSound::ms_backendSDL = NULL;
424 #else
425 extern "C" wxSoundBackend *wxCreateSoundBackendSDL();
426 #endif
427 #endif
428
429 wxSound::wxSound() : m_data(NULL)
430 {
431 }
432
433 wxSound::wxSound(const wxString& sFileName, bool isResource) : m_data(NULL)
434 {
435 Create(sFileName, isResource);
436 }
437
438 wxSound::wxSound(int size, const wxByte* data) : m_data(NULL)
439 {
440 Create(size, data);
441 }
442
443 wxSound::~wxSound()
444 {
445 Free();
446 }
447
448 bool wxSound::Create(const wxString& fileName,
449 bool WXUNUSED_UNLESS_DEBUG(isResource))
450 {
451 wxASSERT_MSG( !isResource,
452 wxT("Loading sound from resources is only supported on Windows") );
453
454 Free();
455
456 wxFile fileWave;
457 if (!fileWave.Open(fileName, wxFile::read))
458 {
459 return false;
460 }
461
462 wxFileOffset lenOrig = fileWave.Length();
463 if ( lenOrig == wxInvalidOffset )
464 return false;
465
466 size_t len = wx_truncate_cast(size_t, lenOrig);
467 wxUint8 *data = new wxUint8[len];
468 if ( fileWave.Read(data, len) != lenOrig )
469 {
470 delete [] data;
471 wxLogError(_("Couldn't load sound data from '%s'."), fileName.c_str());
472 return false;
473 }
474
475 if (!LoadWAV(data, len, false))
476 {
477 delete [] data;
478 wxLogError(_("Sound file '%s' is in unsupported format."),
479 fileName.c_str());
480 return false;
481 }
482
483 return true;
484 }
485
486 bool wxSound::Create(int size, const wxByte* data)
487 {
488 wxASSERT( data != NULL );
489
490 Free();
491 if (!LoadWAV(data, size, true))
492 {
493 wxLogError(_("Sound data are in unsupported format."));
494 return false;
495 }
496 return true;
497 }
498
499 /*static*/ void wxSound::EnsureBackend()
500 {
501 if (!ms_backend)
502 {
503 // FIXME -- make this fully dynamic when plugins architecture is in
504 // place
505 #if wxUSE_LIBSDL
506 //if (!ms_backend)
507 {
508 #if !wxUSE_PLUGINS
509 ms_backend = wxCreateSoundBackendSDL();
510 #else
511 wxString dllname;
512 dllname.Printf(wxT("%s/%s"),
513 wxDynamicLibrary::GetPluginsDirectory().c_str(),
514 wxDynamicLibrary::CanonicalizePluginName(
515 wxT("sound_sdl"), wxDL_PLUGIN_BASE).c_str());
516 wxLogTrace(wxT("sound"),
517 wxT("trying to load SDL plugin from '%s'..."),
518 dllname.c_str());
519 wxLogNull null;
520 ms_backendSDL = new wxDynamicLibrary(dllname, wxDL_NOW);
521 if (!ms_backendSDL->IsLoaded())
522 {
523 wxDELETE(ms_backendSDL);
524 }
525 else
526 {
527 typedef wxSoundBackend *(*wxCreateSoundBackend_t)();
528 wxDYNLIB_FUNCTION(wxCreateSoundBackend_t,
529 wxCreateSoundBackendSDL, *ms_backendSDL);
530 if (pfnwxCreateSoundBackendSDL)
531 {
532 ms_backend = (*pfnwxCreateSoundBackendSDL)();
533 }
534 }
535 #endif
536 if (ms_backend && !ms_backend->IsAvailable())
537 {
538 wxDELETE(ms_backend);
539 }
540 }
541 #endif
542
543 #ifdef HAVE_SYS_SOUNDCARD_H
544 if (!ms_backend)
545 {
546 ms_backend = new wxSoundBackendOSS();
547 if (!ms_backend->IsAvailable())
548 {
549 wxDELETE(ms_backend);
550 }
551 }
552 #endif
553
554 if (!ms_backend)
555 ms_backend = new wxSoundBackendNull();
556
557 if (!ms_backend->HasNativeAsyncPlayback())
558 ms_backend = new wxSoundSyncOnlyAdaptor(ms_backend);
559
560 wxLogTrace(wxT("sound"),
561 wxT("using backend '%s'"), ms_backend->GetName().c_str());
562 }
563 }
564
565 /*static*/ void wxSound::UnloadBackend()
566 {
567 if (ms_backend)
568 {
569 wxLogTrace(wxT("sound"), wxT("unloading backend"));
570
571 Stop();
572
573 wxDELETE(ms_backend);
574 #if wxUSE_LIBSDL && wxUSE_PLUGINS
575 delete ms_backendSDL;
576 #endif
577 }
578 }
579
580 bool wxSound::DoPlay(unsigned flags) const
581 {
582 wxCHECK_MSG( IsOk(), false, wxT("Attempt to play invalid wave data") );
583
584 EnsureBackend();
585 wxSoundPlaybackStatus status;
586 status.m_playing = true;
587 status.m_stopRequested = false;
588 return ms_backend->Play(m_data, flags, &status);
589 }
590
591 /*static*/ void wxSound::Stop()
592 {
593 if (ms_backend)
594 ms_backend->Stop();
595 }
596
597 /*static*/ bool wxSound::IsPlaying()
598 {
599 if (ms_backend)
600 return ms_backend->IsPlaying();
601 else
602 return false;
603 }
604
605 void wxSound::Free()
606 {
607 if (m_data)
608 m_data->DecRef();
609 }
610
611 typedef struct
612 {
613 wxUint32 uiSize;
614 wxUint16 uiFormatTag;
615 wxUint16 uiChannels;
616 wxUint32 ulSamplesPerSec;
617 wxUint32 ulAvgBytesPerSec;
618 wxUint16 uiBlockAlign;
619 wxUint16 uiBitsPerSample;
620 } WAVEFORMAT;
621
622 #define WAVE_FORMAT_PCM 1
623 #define WAVE_INDEX 8
624 #define FMT_INDEX 12
625
626 bool wxSound::LoadWAV(const wxUint8 *data, size_t length, bool copyData)
627 {
628 // the simplest wave file header consists of 44 bytes:
629 //
630 // 0 "RIFF"
631 // 4 file size - 8
632 // 8 "WAVE"
633 //
634 // 12 "fmt "
635 // 16 chunk size |
636 // 20 format tag |
637 // 22 number of channels |
638 // 24 sample rate | WAVEFORMAT
639 // 28 average bytes per second |
640 // 32 bytes per frame |
641 // 34 bits per sample |
642 //
643 // 36 "data"
644 // 40 number of data bytes
645 // 44 (wave signal) data
646 //
647 // so check that we have at least as much
648 if ( length < 44 )
649 return false;
650
651 WAVEFORMAT waveformat;
652 memcpy(&waveformat, &data[FMT_INDEX + 4], sizeof(WAVEFORMAT));
653 waveformat.uiSize = wxUINT32_SWAP_ON_BE(waveformat.uiSize);
654 waveformat.uiFormatTag = wxUINT16_SWAP_ON_BE(waveformat.uiFormatTag);
655 waveformat.uiChannels = wxUINT16_SWAP_ON_BE(waveformat.uiChannels);
656 waveformat.ulSamplesPerSec = wxUINT32_SWAP_ON_BE(waveformat.ulSamplesPerSec);
657 waveformat.ulAvgBytesPerSec = wxUINT32_SWAP_ON_BE(waveformat.ulAvgBytesPerSec);
658 waveformat.uiBlockAlign = wxUINT16_SWAP_ON_BE(waveformat.uiBlockAlign);
659 waveformat.uiBitsPerSample = wxUINT16_SWAP_ON_BE(waveformat.uiBitsPerSample);
660
661 // get the sound data size
662 wxUint32 ul;
663 memcpy(&ul, &data[FMT_INDEX + waveformat.uiSize + 12], 4);
664 ul = wxUINT32_SWAP_ON_BE(ul);
665
666 if ( length < ul + FMT_INDEX + waveformat.uiSize + 16 )
667 return false;
668
669 if (memcmp(data, "RIFF", 4) != 0)
670 return false;
671 if (memcmp(&data[WAVE_INDEX], "WAVE", 4) != 0)
672 return false;
673 if (memcmp(&data[FMT_INDEX], "fmt ", 4) != 0)
674 return false;
675 if (memcmp(&data[FMT_INDEX + waveformat.uiSize + 8], "data", 4) != 0)
676 return false;
677
678 if (waveformat.uiFormatTag != WAVE_FORMAT_PCM)
679 return false;
680
681 if (waveformat.ulSamplesPerSec !=
682 waveformat.ulAvgBytesPerSec / waveformat.uiBlockAlign)
683 return false;
684
685 m_data = new wxSoundData;
686 m_data->m_channels = waveformat.uiChannels;
687 m_data->m_samplingRate = waveformat.ulSamplesPerSec;
688 m_data->m_bitsPerSample = waveformat.uiBitsPerSample;
689 m_data->m_samples = ul / (m_data->m_channels * m_data->m_bitsPerSample / 8);
690 m_data->m_dataBytes = ul;
691
692 if (copyData)
693 {
694 m_data->m_dataWithHeader = new wxUint8[length];
695 memcpy(m_data->m_dataWithHeader, data, length);
696 }
697 else
698 m_data->m_dataWithHeader = (wxUint8*)data;
699
700 m_data->m_data =
701 (&m_data->m_dataWithHeader[FMT_INDEX + waveformat.uiSize + 8]);
702
703 return true;
704 }
705
706
707 // ----------------------------------------------------------------------------
708 // wxSoundCleanupModule
709 // ----------------------------------------------------------------------------
710
711 class wxSoundCleanupModule: public wxModule
712 {
713 public:
714 bool OnInit() { return true; }
715 void OnExit() { wxSound::UnloadBackend(); }
716 DECLARE_DYNAMIC_CLASS(wxSoundCleanupModule)
717 };
718
719 IMPLEMENT_DYNAMIC_CLASS(wxSoundCleanupModule, wxModule)
720
721 #endif