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