]> git.saurik.com Git - wxWidgets.git/blob - src/unix/sound.cpp
added static casts to void * for pointers used with format specifier to silence...
[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 #endif
35
36 #include "wx/thread.h"
37 #include "wx/file.h"
38 #include "wx/module.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 _T("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(_T("sound"), _T("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(_T("sound"), _T("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(_T("sound"), _T("IOCTL failure (SNDCTL_DSP_SAMPLESIZE)"));
208         return false;
209     }
210     if (tmp != data->m_bitsPerSample)
211     {
212         wxLogTrace(_T("sound"),
213                    _T("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(_T("sound"), _T("IOCTL failure (SNDCTL_DSP_STEREO)"));
223         return false;
224     }
225     if (tmp != stereo)
226     {
227         wxLogTrace(_T("sound"), _T("Unable to set DSP to %s."), stereo?  _T("stereo"):_T("mono"));
228         m_needConversion = true;
229     }
230
231     tmp = data->m_samplingRate;
232     if (ioctl(dev, SNDCTL_DSP_SPEED, &tmp) < 0)
233     {
234         wxLogTrace(_T("sound"), _T("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(_T("sound"),
248                        _T("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(_T("sound"), _T("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     ~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(_T("sound"), _T("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(_T("sound"), _T("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(_T("sound"), _T("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(_T("sound"), _T("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              _T("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(_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'..."),
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(_T("sound"),
561                    _T("using backend '%s'"), ms_backend->GetName().c_str());
562     }
563 }
564
565 /*static*/ void wxSound::UnloadBackend()
566 {
567     if (ms_backend)
568     {
569         wxLogTrace(_T("sound"), _T("unloading backend"));
570
571         Stop();
572
573         delete ms_backend;
574         ms_backend = NULL;
575 #if wxUSE_LIBSDL && wxUSE_PLUGINS
576         delete ms_backendSDL;
577 #endif
578     }
579 }
580
581 bool wxSound::DoPlay(unsigned flags) const
582 {
583     wxCHECK_MSG( IsOk(), false, _T("Attempt to play invalid wave data") );
584
585     EnsureBackend();
586     wxSoundPlaybackStatus status;
587     status.m_playing = true;
588     status.m_stopRequested = false;
589     return ms_backend->Play(m_data, flags, &status);
590 }
591
592 /*static*/ void wxSound::Stop()
593 {
594     if (ms_backend)
595         ms_backend->Stop();
596 }
597
598 /*static*/ bool wxSound::IsPlaying()
599 {
600     if (ms_backend)
601         return ms_backend->IsPlaying();
602     else
603         return false;
604 }
605
606 void wxSound::Free()
607 {
608     if (m_data)
609         m_data->DecRef();
610 }
611
612 typedef struct
613 {
614     wxUint32      uiSize;
615     wxUint16      uiFormatTag;
616     wxUint16      uiChannels;
617     wxUint32      ulSamplesPerSec;
618     wxUint32      ulAvgBytesPerSec;
619     wxUint16      uiBlockAlign;
620     wxUint16      uiBitsPerSample;
621 } WAVEFORMAT;
622
623 #define WAVE_FORMAT_PCM  1
624 #define WAVE_INDEX       8
625 #define FMT_INDEX       12
626
627 bool wxSound::LoadWAV(const wxUint8 *data, size_t length, bool copyData)
628 {
629     // the simplest wave file header consists of 44 bytes:
630     //
631     //      0   "RIFF"
632     //      4   file size - 8
633     //      8   "WAVE"
634     //
635     //      12  "fmt "
636     //      16  chunk size                  |
637     //      20  format tag                  |
638     //      22  number of channels          |
639     //      24  sample rate                 | WAVEFORMAT
640     //      28  average bytes per second    |
641     //      32  bytes per frame             |
642     //      34  bits per sample             |
643     //  
644     //      36  "data"
645     //      40  number of data bytes
646     //      44  (wave signal) data
647     //
648     // so check that we have at least as much
649     if ( length < 44 )
650         return false;
651
652     WAVEFORMAT waveformat;
653     memcpy(&waveformat, &data[FMT_INDEX + 4], sizeof(WAVEFORMAT));
654     waveformat.uiSize = wxUINT32_SWAP_ON_BE(waveformat.uiSize);
655     waveformat.uiFormatTag = wxUINT16_SWAP_ON_BE(waveformat.uiFormatTag);
656     waveformat.uiChannels = wxUINT16_SWAP_ON_BE(waveformat.uiChannels);
657     waveformat.ulSamplesPerSec = wxUINT32_SWAP_ON_BE(waveformat.ulSamplesPerSec);
658     waveformat.ulAvgBytesPerSec = wxUINT32_SWAP_ON_BE(waveformat.ulAvgBytesPerSec);
659     waveformat.uiBlockAlign = wxUINT16_SWAP_ON_BE(waveformat.uiBlockAlign);
660     waveformat.uiBitsPerSample = wxUINT16_SWAP_ON_BE(waveformat.uiBitsPerSample);
661
662     // get the sound data size
663     wxUint32 ul;
664     memcpy(&ul, &data[FMT_INDEX + waveformat.uiSize + 12], 4);
665     ul = wxUINT32_SWAP_ON_BE(ul);
666
667     if ( length < ul + FMT_INDEX + waveformat.uiSize + 16 )
668         return false;
669
670     if (memcmp(data, "RIFF", 4) != 0)
671         return false;
672     if (memcmp(&data[WAVE_INDEX], "WAVE", 4) != 0)
673         return false;
674     if (memcmp(&data[FMT_INDEX], "fmt ", 4) != 0)
675         return false;
676     if (memcmp(&data[FMT_INDEX + waveformat.uiSize + 8], "data", 4) != 0)
677         return false;
678
679     if (waveformat.uiFormatTag != WAVE_FORMAT_PCM)
680         return false;
681
682     if (waveformat.ulSamplesPerSec !=
683         waveformat.ulAvgBytesPerSec / waveformat.uiBlockAlign)
684         return false;
685
686     m_data = new wxSoundData;
687     m_data->m_channels = waveformat.uiChannels;
688     m_data->m_samplingRate = waveformat.ulSamplesPerSec;
689     m_data->m_bitsPerSample = waveformat.uiBitsPerSample;
690     m_data->m_samples = ul / (m_data->m_channels * m_data->m_bitsPerSample / 8);
691     m_data->m_dataBytes = ul;
692
693     if (copyData)
694     {
695         m_data->m_dataWithHeader = new wxUint8[length];
696         memcpy(m_data->m_dataWithHeader, data, length);
697     }
698     else
699         m_data->m_dataWithHeader = (wxUint8*)data;
700
701     m_data->m_data =
702         (&m_data->m_dataWithHeader[FMT_INDEX + waveformat.uiSize + 8]);
703
704     return true;
705 }
706
707
708 // ----------------------------------------------------------------------------
709 // wxSoundCleanupModule
710 // ----------------------------------------------------------------------------
711
712 class wxSoundCleanupModule: public wxModule
713 {
714 public:
715     bool OnInit() { return true; }
716     void OnExit() { wxSound::UnloadBackend(); }
717     DECLARE_DYNAMIC_CLASS(wxSoundCleanupModule)
718 };
719
720 IMPLEMENT_DYNAMIC_CLASS(wxSoundCleanupModule, wxModule)
721
722 #endif