]> git.saurik.com Git - wxWidgets.git/blame - src/unix/sound.cpp
Cleanup
[wxWidgets.git] / src / unix / sound.cpp
CommitLineData
9be32e8f
VS
1/////////////////////////////////////////////////////////////////////////////
2// Name: sound.cpp
3// Purpose: wxSound
4// Author: Marcel Rasche, Vaclav Slavik
5// Modified by:
6// Created: 25/10/98
7// RCS-ID: $Id$
f156e20c 8// Copyright: (c) Julian Smart, Open Source Applications Foundation
9be32e8f
VS
9// Licence: wxWindows licence
10/////////////////////////////////////////////////////////////////////////////
11
12#if defined(__GNUG__) && !defined(NO_GCC_PRAGMA)
13#pragma implementation "sound.h"
14#pragma implementation "soundbase.h"
15#endif
16
17// for compilers that support precompilation, includes "wx.h".
18#include "wx/wxprec.h"
19
20#include "wx/setup.h"
21
22#if defined(__BORLANDC__)
23#pragma hdrstop
24#endif
25
f156e20c 26#if wxUSE_SOUND
9be32e8f
VS
27
28#include <stdio.h>
29#include <unistd.h>
30#include <fcntl.h>
31#include <sys/ioctl.h>
32
83f7f12d 33#ifdef HAVE_SYS_SOUNDCARD_H
9be32e8f
VS
34#include <sys/soundcard.h>
35#endif
36
37#ifndef WX_PRECOMP
38 #include "wx/event.h"
39 #include "wx/intl.h"
40 #include "wx/log.h"
41#endif
42
43#include "wx/thread.h"
44#include "wx/file.h"
45#include "wx/module.h"
46#include "wx/sound.h"
47#include "wx/dynlib.h"
48
83f7f12d
VS
49
50#if wxUSE_THREADS
51// mutex for all wxSound's synchronization
52static wxMutex gs_soundMutex;
53#endif
54
55// ----------------------------------------------------------------------------
56// wxSoundData
57// ----------------------------------------------------------------------------
58
59void wxSoundData::IncRef()
60{
61#if wxUSE_THREADS
62 wxMutexLocker locker(gs_soundMutex);
63#endif
64 m_refCnt++;
65}
66
67void wxSoundData::DecRef()
68{
69#if wxUSE_THREADS
70 wxMutexLocker locker(gs_soundMutex);
71#endif
72 if (--m_refCnt == 0)
73 delete this;
74}
75
76wxSoundData::~wxSoundData()
77{
78 delete[] m_dataWithHeader;
79}
80
81
9be32e8f
VS
82// ----------------------------------------------------------------------------
83// wxSoundBackendNull, used in absence of audio API or card
84// ----------------------------------------------------------------------------
85
86class wxSoundBackendNull : public wxSoundBackend
87{
88public:
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; }
83f7f12d
VS
93 bool Play(wxSoundData *WXUNUSED(data), unsigned WXUNUSED(flags),
94 volatile wxSoundPlaybackStatus *WXUNUSED(status))
9be32e8f 95 { return true; }
83f7f12d
VS
96 void Stop() {}
97 bool IsPlaying() const { return false; }
9be32e8f
VS
98};
99
100
101// ----------------------------------------------------------------------------
102// wxSoundBackendOSS, for Linux
103// ----------------------------------------------------------------------------
104
105#ifdef HAVE_SYS_SOUNDCARD_H
106
107#ifndef AUDIODEV
108#define AUDIODEV "/dev/dsp" // Default path for audio device
109#endif
110
111class wxSoundBackendOSS : public wxSoundBackend
112{
113public:
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; }
83f7f12d
VS
118 bool Play(wxSoundData *data, unsigned flags,
119 volatile wxSoundPlaybackStatus *status);
120 void Stop() {}
121 bool IsPlaying() const { return false; }
9be32e8f
VS
122
123private:
124 int OpenDSP(const wxSoundData *data);
544c4a3b 125 bool InitDSP(int dev, const wxSoundData *data);
9be32e8f
VS
126
127 int m_DSPblkSize; // Size of the DSP buffer
544c4a3b 128 bool m_needConversion;
9be32e8f
VS
129};
130
131bool wxSoundBackendOSS::IsAvailable() const
132{
133 int fd;
134 fd = open(AUDIODEV, O_WRONLY | O_NONBLOCK);
135 if (fd < 0)
136 return false;
137 close(fd);
138 return true;
139}
140
83f7f12d
VS
141bool wxSoundBackendOSS::Play(wxSoundData *data, unsigned flags,
142 volatile wxSoundPlaybackStatus *status)
9be32e8f
VS
143{
144 int dev = OpenDSP(data);
145
146 if (dev < 0)
147 return false;
148
149 ioctl(dev, SNDCTL_DSP_SYNC, 0);
544c4a3b 150
9be32e8f
VS
151 do
152 {
153 bool play = true;
154 int i;
155 unsigned l = 0;
156 size_t datasize = data->m_dataBytes;
157
158 do
159 {
83f7f12d
VS
160 if (status->m_stopRequested)
161 {
162 wxLogTrace(_T("sound"), _T("playback stopped"));
163 close(dev);
164 return true;
165 }
166
9be32e8f 167 i= (int)((l + m_DSPblkSize) < datasize ?
544c4a3b 168 m_DSPblkSize : (datasize - l));
9be32e8f
VS
169 if (write(dev, &data->m_data[l], i) != i)
170 {
171 play = false;
172 }
173 l += i;
174 } while (play && l < datasize);
175 } while (flags & wxSOUND_LOOP);
83f7f12d 176
544c4a3b 177 close(dev);
9be32e8f
VS
178 return true;
179}
180
181int wxSoundBackendOSS::OpenDSP(const wxSoundData *data)
182{
183 int dev = -1;
184
185 if ((dev = open(AUDIODEV, O_WRONLY, 0)) <0)
186 return -1;
187
544c4a3b 188 if (!InitDSP(dev, data) || m_needConversion)
9be32e8f
VS
189 {
190 close(dev);
191 return -1;
192 }
193
194 return dev;
195}
196
544c4a3b
RD
197
198bool wxSoundBackendOSS::InitDSP(int dev, const wxSoundData *data)
9be32e8f 199{
544c4a3b
RD
200 unsigned tmp;
201
202 // Reset the dsp
203 if (ioctl(dev, SNDCTL_DSP_RESET, 0) < 0)
204 {
205 wxLogTrace(_T("sound"), _T("unable to reset dsp"));
9be32e8f 206 return false;
544c4a3b
RD
207 }
208
209 m_needConversion = false;
210
211 tmp = data->m_bitsPerSample;
212 if (ioctl(dev, SNDCTL_DSP_SAMPLESIZE, &tmp) < 0)
213 {
214 wxLogTrace(_T("sound"), _T("IOCTL failure (SNDCTL_DSP_SAMPLESIZE)"));
9be32e8f 215 return false;
544c4a3b
RD
216 }
217 if (tmp != data->m_bitsPerSample)
218 {
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;
223 }
224
225 unsigned stereo = data->m_channels == 1 ? 0 : 1;
226 tmp = stereo;
227 if (ioctl(dev, SNDCTL_DSP_STEREO, &tmp) < 0)
228 {
229 wxLogTrace(_T("sound"), _T("IOCTL failure (SNDCTL_DSP_STEREO)"));
9be32e8f 230 return false;
544c4a3b
RD
231 }
232 if (tmp != stereo)
233 {
234 wxLogTrace(_T("sound"), _T("Unable to set DSP to %s."), stereo? _T("stereo"):_T("mono"));
235 m_needConversion = true;
236 }
237
238 tmp = data->m_samplingRate;
239 if (ioctl(dev, SNDCTL_DSP_SPEED, &tmp) < 0)
240 {
241 wxLogTrace(_T("sound"), _T("IOCTL failure (SNDCTL_DSP_SPEED)"));
242 return false;
243 }
244 if (tmp != data->m_samplingRate)
245 {
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;
258 }
259 }
260
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)
264 {
265 wxLogTrace(_T("sound"), _T("IOCTL failure (SNDCTL_DSP_GETBLKSIZE)"));
9be32e8f 266 return false;
544c4a3b 267 }
9be32e8f
VS
268 return true;
269}
544c4a3b 270
9be32e8f
VS
271#endif // HAVE_SYS_SOUNDCARD_H
272
9be32e8f 273// ----------------------------------------------------------------------------
83f7f12d 274// wxSoundSyncOnlyAdaptor
9be32e8f
VS
275// ----------------------------------------------------------------------------
276
277#if wxUSE_THREADS
278
83f7f12d 279class wxSoundSyncOnlyAdaptor;
9be32e8f
VS
280
281// this class manages asynchronous playback of audio if the backend doesn't
282// support it natively (e.g. OSS backend)
283class wxSoundAsyncPlaybackThread : public wxThread
284{
285public:
83f7f12d 286 wxSoundAsyncPlaybackThread(wxSoundSyncOnlyAdaptor *adaptor,
9be32e8f 287 wxSoundData *data, unsigned flags)
83f7f12d
VS
288 : wxThread(), m_adapt(adaptor), m_data(data), m_flags(flags) {}
289 virtual ExitCode Entry();
9be32e8f
VS
290
291protected:
83f7f12d 292 wxSoundSyncOnlyAdaptor *m_adapt;
9be32e8f
VS
293 wxSoundData *m_data;
294 unsigned m_flags;
295};
296
297#endif // wxUSE_THREADS
298
83f7f12d
VS
299// This class turns wxSoundBackend that doesn't support asynchronous playback
300// into one that does
301class wxSoundSyncOnlyAdaptor : public wxSoundBackend
302{
303public:
304 wxSoundSyncOnlyAdaptor(wxSoundBackend *backend)
305 : m_backend(backend), m_playing(false) {}
306 ~wxSoundSyncOnlyAdaptor()
307 {
308 delete m_backend;
309 }
310 wxString GetName() const
311 {
312 return m_backend->GetName();
313 }
314 int GetPriority() const
315 {
316 return m_backend->GetPriority();
317 }
318 bool IsAvailable() const
319 {
320 return m_backend->IsAvailable();
321 }
322 bool HasNativeAsyncPlayback() const
323 {
324 return true;
325 }
326 bool Play(wxSoundData *data, unsigned flags,
327 volatile wxSoundPlaybackStatus *status);
328 void Stop();
329 bool IsPlaying() const;
330
331private:
332 friend class wxSoundAsyncPlaybackThread;
333
334 wxSoundBackend *m_backend;
335 bool m_playing;
336#if wxUSE_THREADS
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;
341#endif
342};
343
344
345#if wxUSE_THREADS
346wxThread::ExitCode wxSoundAsyncPlaybackThread::Entry()
347{
348 m_adapt->m_backend->Play(m_data, m_flags & ~wxSOUND_ASYNC,
349 &m_adapt->m_status);
350
351 m_data->DecRef();
352 m_adapt->m_playing = false;
353 m_adapt->m_mutexRightToPlay.Unlock();
354 wxLogTrace(_T("sound"), _T("terminated async playback thread"));
355 return 0;
356}
357#endif
358
359bool wxSoundSyncOnlyAdaptor::Play(wxSoundData *data, unsigned flags,
360 volatile wxSoundPlaybackStatus *status)
361{
362 Stop();
363 if (flags & wxSOUND_ASYNC)
364 {
365#if wxUSE_THREADS
366 m_mutexRightToPlay.Lock();
367 m_status.m_playing = true;
368 m_status.m_stopRequested = false;
369 data->IncRef();
370 wxThread *th = new wxSoundAsyncPlaybackThread(this, data, flags);
371 th->Create();
372 th->Run();
373 wxLogTrace(_T("sound"), _T("launched async playback thread"));
374 return true;
375#else
376 wxLogError(_("Unable to play sound asynchronously."));
377 return false;
378#endif
379 }
380 else
381 {
382#if wxUSE_THREADS
383 m_mutexRightToPlay.Lock();
384#endif
385 bool rv = m_backend->Play(data, flags, status);
386#if wxUSE_THREADS
387 m_mutexRightToPlay.Unlock();
388#endif
389 return rv;
390 }
391}
392
393void wxSoundSyncOnlyAdaptor::Stop()
394{
395 wxLogTrace(_T("sound"), _T("asking audio to stop"));
a333f34d
RR
396
397#if wxUSE_THREADS
83f7f12d
VS
398 // tell the player thread (if running) to stop playback ASAP:
399 m_status.m_stopRequested = true;
400
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"));
a333f34d 408#endif
83f7f12d
VS
409}
410
411bool wxSoundSyncOnlyAdaptor::IsPlaying() const
412{
a333f34d 413#if wxUSE_THREADS
83f7f12d 414 return m_status.m_playing;
a333f34d
RR
415#else
416 return FALSE;
417#endif
83f7f12d
VS
418}
419
420
9be32e8f
VS
421// ----------------------------------------------------------------------------
422// wxSound
423// ----------------------------------------------------------------------------
424
425wxSoundBackend *wxSound::ms_backend = NULL;
426
427// FIXME - temporary, until we have plugins architecture
428#if wxUSE_LIBSDL
429 #if wxUSE_PLUGINS
430 wxDynamicLibrary *wxSound::ms_backendSDL = NULL;
431 #else
432 extern "C" wxSoundBackend *wxCreateSoundBackendSDL();
433 #endif
434#endif
435
436wxSound::wxSound() : m_data(NULL)
437{
438}
439
440wxSound::wxSound(const wxString& sFileName, bool isResource) : m_data(NULL)
441{
442 Create(sFileName, isResource);
443}
444
445wxSound::wxSound(int size, const wxByte* data) : m_data(NULL)
446{
447 Create(size, data);
448}
449
450wxSound::~wxSound()
451{
452 Free();
453}
454
455bool wxSound::Create(const wxString& fileName, bool isResource)
456{
457 wxASSERT_MSG( !isResource,
458 _T("Loading sound from resources is only supported on Windows") );
459
460 Free();
461
462 wxFile fileWave;
463 if (!fileWave.Open(fileName, wxFile::read))
464 {
465 return false;
466 }
467
468 size_t len = fileWave.Length();
469 wxUint8 *data = new wxUint8[len];
470 if (fileWave.Read(data, len) != len)
471 {
472 wxLogError(_("Couldn't load sound data from '%s'."), fileName.c_str());
473 return false;
474 }
475
476 if (!LoadWAV(data, len, false))
477 {
478 wxLogError(_("Sound file '%s' is in unsupported format."),
479 fileName.c_str());
480 return false;
481 }
482
483 return true;
484}
485
486bool 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
9be32e8f 505#if wxUSE_LIBSDL
3209f765 506 //if (!ms_backend)
9be32e8f
VS
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
3209f765
VS
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
9be32e8f
VS
554 if (!ms_backend)
555 ms_backend = new wxSoundBackendNull();
556
83f7f12d
VS
557 if (!ms_backend->HasNativeAsyncPlayback())
558 ms_backend = new wxSoundSyncOnlyAdaptor(ms_backend);
559
9be32e8f
VS
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"));
83f7f12d
VS
570
571 Stop();
572
9be32e8f
VS
573 delete ms_backend;
574 ms_backend = NULL;
575#if wxUSE_LIBSDL && wxUSE_PLUGINS
576 delete ms_backendSDL;
577#endif
578 }
579}
580
f156e20c 581bool wxSound::DoPlay(unsigned flags) const
9be32e8f
VS
582{
583 wxCHECK_MSG( IsOk(), false, _T("Attempt to play invalid wave data") );
584
585 EnsureBackend();
83f7f12d
VS
586 wxSoundPlaybackStatus status;
587 status.m_playing = true;
588 status.m_stopRequested = false;
589 return ms_backend->Play(m_data, flags, &status);
590}
9be32e8f 591
83f7f12d
VS
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();
9be32e8f 602 else
83f7f12d 603 return false;
9be32e8f
VS
604}
605
606void wxSound::Free()
607{
9be32e8f
VS
608 if (m_data)
609 m_data->DecRef();
610}
611
612typedef struct
613{
614 wxUint32 uiSize;
615 wxUint16 uiFormatTag;
616 wxUint16 uiChannels;
617 wxUint32 ulSamplesPerSec;
618 wxUint32 ulAvgBytesPerSec;
619 wxUint16 uiBlockAlign;
620 wxUint16 uiBitsPerSample;
83f7f12d 621} WAVEFORMAT;
9be32e8f
VS
622
623#define MONO 1 // and stereo is 2 by wav format
624#define WAVE_FORMAT_PCM 1
625#define WAVE_INDEX 8
626#define FMT_INDEX 12
627
628bool wxSound::LoadWAV(const wxUint8 *data, size_t length, bool copyData)
629{
630 WAVEFORMAT waveformat;
631 wxUint32 ul;
632
633 if (length < 32 + sizeof(WAVEFORMAT))
634 return false;
635
636 memcpy(&waveformat, &data[FMT_INDEX + 4], sizeof(WAVEFORMAT));
637 waveformat.uiSize = wxUINT32_SWAP_ON_BE(waveformat.uiSize);
638 waveformat.uiFormatTag = wxUINT16_SWAP_ON_BE(waveformat.uiFormatTag);
639 waveformat.uiChannels = wxUINT16_SWAP_ON_BE(waveformat.uiChannels);
640 waveformat.ulSamplesPerSec = wxUINT32_SWAP_ON_BE(waveformat.ulSamplesPerSec);
641 waveformat.ulAvgBytesPerSec = wxUINT32_SWAP_ON_BE(waveformat.ulAvgBytesPerSec);
642 waveformat.uiBlockAlign = wxUINT16_SWAP_ON_BE(waveformat.uiBlockAlign);
643 waveformat.uiBitsPerSample = wxUINT16_SWAP_ON_BE(waveformat.uiBitsPerSample);
644
645 if (memcmp(data, "RIFF", 4) != 0)
646 return false;
647 if (memcmp(&data[WAVE_INDEX], "WAVE", 4) != 0)
648 return false;
649 if (memcmp(&data[FMT_INDEX], "fmt ", 4) != 0)
650 return false;
651 if (memcmp(&data[FMT_INDEX + waveformat.uiSize + 8], "data", 4) != 0)
652 return false;
653 memcpy(&ul,&data[FMT_INDEX + waveformat.uiSize + 12], 4);
654 ul = wxUINT32_SWAP_ON_BE(ul);
655
656 //WAS: if (ul + FMT_INDEX + waveformat.uiSize + 16 != length)
657 if (ul + FMT_INDEX + waveformat.uiSize + 16 > length)
658 return false;
659
660 if (waveformat.uiFormatTag != WAVE_FORMAT_PCM)
661 return false;
662
663 if (waveformat.ulSamplesPerSec !=
664 waveformat.ulAvgBytesPerSec / waveformat.uiBlockAlign)
665 return false;
666
667 m_data = new wxSoundData;
668 m_data->m_channels = waveformat.uiChannels;
669 m_data->m_samplingRate = waveformat.ulSamplesPerSec;
670 m_data->m_bitsPerSample = waveformat.uiBitsPerSample;
671 m_data->m_samples = ul / (m_data->m_channels * m_data->m_bitsPerSample / 8);
672 m_data->m_dataBytes = ul;
673
674 if (copyData)
675 {
676 m_data->m_dataWithHeader = new wxUint8[length];
677 memcpy(m_data->m_dataWithHeader, data, length);
678 }
679 else
680 m_data->m_dataWithHeader = (wxUint8*)data;
681
682 m_data->m_data =
683 (&m_data->m_dataWithHeader[FMT_INDEX + waveformat.uiSize + 8]);
684
685 return true;
686}
687
688
689// ----------------------------------------------------------------------------
690// wxSoundCleanupModule
691// ----------------------------------------------------------------------------
692
693class wxSoundCleanupModule: public wxModule
694{
695public:
696 bool OnInit() { return true; }
697 void OnExit() { wxSound::UnloadBackend(); }
698 DECLARE_DYNAMIC_CLASS(wxSoundCleanupModule)
699};
700
701IMPLEMENT_DYNAMIC_CLASS(wxSoundCleanupModule, wxModule)
702
703#endif