try SDL backend before OSS, it works better (temporary solution, will need better...
[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 #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
26 #if wxUSE_SOUND
27
28 #include <stdio.h>
29 #include <unistd.h>
30 #include <fcntl.h>
31 #include <sys/ioctl.h>
32
33 #ifdef HAVE_SYS_SOUNDCARD_H
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
49
50 #if wxUSE_THREADS
51 // mutex for all wxSound's synchronization
52 static wxMutex gs_soundMutex;
53 #endif
54
55 // ----------------------------------------------------------------------------
56 // wxSoundData
57 // ----------------------------------------------------------------------------
58
59 void wxSoundData::IncRef()
60 {
61 #if wxUSE_THREADS
62 wxMutexLocker locker(gs_soundMutex);
63 #endif
64 m_refCnt++;
65 }
66
67 void wxSoundData::DecRef()
68 {
69 #if wxUSE_THREADS
70 wxMutexLocker locker(gs_soundMutex);
71 #endif
72 if (--m_refCnt == 0)
73 delete this;
74 }
75
76 wxSoundData::~wxSoundData()
77 {
78 delete[] m_dataWithHeader;
79 }
80
81
82 // ----------------------------------------------------------------------------
83 // wxSoundBackendNull, used in absence of audio API or card
84 // ----------------------------------------------------------------------------
85
86 class wxSoundBackendNull : public wxSoundBackend
87 {
88 public:
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))
95 { return true; }
96 void Stop() {}
97 bool IsPlaying() const { return false; }
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
111 class wxSoundBackendOSS : public wxSoundBackend
112 {
113 public:
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);
120 void Stop() {}
121 bool IsPlaying() const { return false; }
122
123 private:
124 int OpenDSP(const wxSoundData *data);
125 bool InitDSP(int dev, int iDataBits, int iChannel,
126 unsigned long ulSamplingRate);
127
128 int m_DSPblkSize; // Size of the DSP buffer
129 };
130
131 bool 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
141 bool wxSoundBackendOSS::Play(wxSoundData *data, unsigned flags,
142 volatile wxSoundPlaybackStatus *status)
143 {
144 int dev = OpenDSP(data);
145
146 if (dev < 0)
147 return false;
148
149 ioctl(dev, SNDCTL_DSP_SYNC, 0);
150
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 {
160 if (status->m_stopRequested)
161 {
162 wxLogTrace(_T("sound"), _T("playback stopped"));
163 close(dev);
164 return true;
165 }
166
167 i= (int)((l + m_DSPblkSize) < datasize ?
168 m_DSPblkSize : (datasize - l));
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);
176
177 close(dev);
178
179 return true;
180 }
181
182 int wxSoundBackendOSS::OpenDSP(const wxSoundData *data)
183 {
184 int dev = -1;
185
186 if ((dev = open(AUDIODEV, O_WRONLY, 0)) <0)
187 return -1;
188
189 if (!InitDSP(dev,
190 (int)data->m_bitsPerSample,
191 data->m_channels == 1 ? 0 : 1,
192 data->m_samplingRate))
193 {
194 close(dev);
195 return -1;
196 }
197
198 return dev;
199 }
200
201 bool wxSoundBackendOSS::InitDSP(int dev, int iDataBits, int iChannel,
202 unsigned long ulSamplingRate)
203 {
204 if (ioctl(dev, SNDCTL_DSP_GETBLKSIZE, &m_DSPblkSize) < 0)
205 return false;
206 wxLogTrace(_T("sound"), _T("OSS block size: %i"), m_DSPblkSize);
207 if (m_DSPblkSize < 4096 || m_DSPblkSize > 65536)
208 return false;
209 if (ioctl(dev, SNDCTL_DSP_SAMPLESIZE, &iDataBits) < 0)
210 return false;
211 if (ioctl(dev, SNDCTL_DSP_STEREO, &iChannel) < 0)
212 return false;
213 if (ioctl(dev, SNDCTL_DSP_SPEED, &ulSamplingRate) < 0)
214 return false;
215 return true;
216 }
217
218 #endif // HAVE_SYS_SOUNDCARD_H
219
220 // ----------------------------------------------------------------------------
221 // wxSoundSyncOnlyAdaptor
222 // ----------------------------------------------------------------------------
223
224 #if wxUSE_THREADS
225
226 class wxSoundSyncOnlyAdaptor;
227
228 // this class manages asynchronous playback of audio if the backend doesn't
229 // support it natively (e.g. OSS backend)
230 class wxSoundAsyncPlaybackThread : public wxThread
231 {
232 public:
233 wxSoundAsyncPlaybackThread(wxSoundSyncOnlyAdaptor *adaptor,
234 wxSoundData *data, unsigned flags)
235 : wxThread(), m_adapt(adaptor), m_data(data), m_flags(flags) {}
236 virtual ExitCode Entry();
237
238 protected:
239 wxSoundSyncOnlyAdaptor *m_adapt;
240 wxSoundData *m_data;
241 unsigned m_flags;
242 };
243
244 #endif // wxUSE_THREADS
245
246 // This class turns wxSoundBackend that doesn't support asynchronous playback
247 // into one that does
248 class wxSoundSyncOnlyAdaptor : public wxSoundBackend
249 {
250 public:
251 wxSoundSyncOnlyAdaptor(wxSoundBackend *backend)
252 : m_backend(backend), m_playing(false) {}
253 ~wxSoundSyncOnlyAdaptor()
254 {
255 delete m_backend;
256 }
257 wxString GetName() const
258 {
259 return m_backend->GetName();
260 }
261 int GetPriority() const
262 {
263 return m_backend->GetPriority();
264 }
265 bool IsAvailable() const
266 {
267 return m_backend->IsAvailable();
268 }
269 bool HasNativeAsyncPlayback() const
270 {
271 return true;
272 }
273 bool Play(wxSoundData *data, unsigned flags,
274 volatile wxSoundPlaybackStatus *status);
275 void Stop();
276 bool IsPlaying() const;
277
278 private:
279 friend class wxSoundAsyncPlaybackThread;
280
281 wxSoundBackend *m_backend;
282 bool m_playing;
283 #if wxUSE_THREADS
284 // player thread holds this mutex and releases it after it finishes
285 // playing, so that the main thread knows when it can play sound
286 wxMutex m_mutexRightToPlay;
287 wxSoundPlaybackStatus m_status;
288 #endif
289 };
290
291
292 #if wxUSE_THREADS
293 wxThread::ExitCode wxSoundAsyncPlaybackThread::Entry()
294 {
295 m_adapt->m_backend->Play(m_data, m_flags & ~wxSOUND_ASYNC,
296 &m_adapt->m_status);
297
298 m_data->DecRef();
299 m_adapt->m_playing = false;
300 m_adapt->m_mutexRightToPlay.Unlock();
301 wxLogTrace(_T("sound"), _T("terminated async playback thread"));
302 return 0;
303 }
304 #endif
305
306 bool wxSoundSyncOnlyAdaptor::Play(wxSoundData *data, unsigned flags,
307 volatile wxSoundPlaybackStatus *status)
308 {
309 Stop();
310 if (flags & wxSOUND_ASYNC)
311 {
312 #if wxUSE_THREADS
313 m_mutexRightToPlay.Lock();
314 m_status.m_playing = true;
315 m_status.m_stopRequested = false;
316 data->IncRef();
317 wxThread *th = new wxSoundAsyncPlaybackThread(this, data, flags);
318 th->Create();
319 th->Run();
320 wxLogTrace(_T("sound"), _T("launched async playback thread"));
321 return true;
322 #else
323 wxLogError(_("Unable to play sound asynchronously."));
324 return false;
325 #endif
326 }
327 else
328 {
329 #if wxUSE_THREADS
330 m_mutexRightToPlay.Lock();
331 #endif
332 bool rv = m_backend->Play(data, flags, status);
333 #if wxUSE_THREADS
334 m_mutexRightToPlay.Unlock();
335 #endif
336 return rv;
337 }
338 }
339
340 void wxSoundSyncOnlyAdaptor::Stop()
341 {
342 wxLogTrace(_T("sound"), _T("asking audio to stop"));
343
344 #if wxUSE_THREADS
345 // tell the player thread (if running) to stop playback ASAP:
346 m_status.m_stopRequested = true;
347
348 // acquire the mutex to be sure no sound is being played, then
349 // release it because we don't need it for anything (the effect of this
350 // is that calling thread will wait until playback thread reacts to
351 // our request to interrupt playback):
352 m_mutexRightToPlay.Lock();
353 m_mutexRightToPlay.Unlock();
354 wxLogTrace(_T("sound"), _T("audio was stopped"));
355 #endif
356 }
357
358 bool wxSoundSyncOnlyAdaptor::IsPlaying() const
359 {
360 #if wxUSE_THREADS
361 return m_status.m_playing;
362 #else
363 return FALSE;
364 #endif
365 }
366
367
368 // ----------------------------------------------------------------------------
369 // wxSound
370 // ----------------------------------------------------------------------------
371
372 wxSoundBackend *wxSound::ms_backend = NULL;
373
374 // FIXME - temporary, until we have plugins architecture
375 #if wxUSE_LIBSDL
376 #if wxUSE_PLUGINS
377 wxDynamicLibrary *wxSound::ms_backendSDL = NULL;
378 #else
379 extern "C" wxSoundBackend *wxCreateSoundBackendSDL();
380 #endif
381 #endif
382
383 wxSound::wxSound() : m_data(NULL)
384 {
385 }
386
387 wxSound::wxSound(const wxString& sFileName, bool isResource) : m_data(NULL)
388 {
389 Create(sFileName, isResource);
390 }
391
392 wxSound::wxSound(int size, const wxByte* data) : m_data(NULL)
393 {
394 Create(size, data);
395 }
396
397 wxSound::~wxSound()
398 {
399 Free();
400 }
401
402 bool wxSound::Create(const wxString& fileName, bool isResource)
403 {
404 wxASSERT_MSG( !isResource,
405 _T("Loading sound from resources is only supported on Windows") );
406
407 Free();
408
409 wxFile fileWave;
410 if (!fileWave.Open(fileName, wxFile::read))
411 {
412 return false;
413 }
414
415 size_t len = fileWave.Length();
416 wxUint8 *data = new wxUint8[len];
417 if (fileWave.Read(data, len) != len)
418 {
419 wxLogError(_("Couldn't load sound data from '%s'."), fileName.c_str());
420 return false;
421 }
422
423 if (!LoadWAV(data, len, false))
424 {
425 wxLogError(_("Sound file '%s' is in unsupported format."),
426 fileName.c_str());
427 return false;
428 }
429
430 return true;
431 }
432
433 bool wxSound::Create(int size, const wxByte* data)
434 {
435 wxASSERT( data != NULL );
436
437 Free();
438 if (!LoadWAV(data, size, true))
439 {
440 wxLogError(_("Sound data are in unsupported format."));
441 return false;
442 }
443 return true;
444 }
445
446 /*static*/ void wxSound::EnsureBackend()
447 {
448 if (!ms_backend)
449 {
450 // FIXME -- make this fully dynamic when plugins architecture is in
451 // place
452 #if wxUSE_LIBSDL
453 //if (!ms_backend)
454 {
455 #if !wxUSE_PLUGINS
456 ms_backend = wxCreateSoundBackendSDL();
457 #else
458 wxString dllname;
459 dllname.Printf(_T("%s/%s"),
460 wxDynamicLibrary::GetPluginsDirectory().c_str(),
461 wxDynamicLibrary::CanonicalizePluginName(
462 _T("sound_sdl"), wxDL_PLUGIN_BASE).c_str());
463 wxLogTrace(_T("sound"),
464 _T("trying to load SDL plugin from '%s'..."),
465 dllname.c_str());
466 wxLogNull null;
467 ms_backendSDL = new wxDynamicLibrary(dllname, wxDL_NOW);
468 if (!ms_backendSDL->IsLoaded())
469 {
470 wxDELETE(ms_backendSDL);
471 }
472 else
473 {
474 typedef wxSoundBackend *(*wxCreateSoundBackend_t)();
475 wxDYNLIB_FUNCTION(wxCreateSoundBackend_t,
476 wxCreateSoundBackendSDL, *ms_backendSDL);
477 if (pfnwxCreateSoundBackendSDL)
478 {
479 ms_backend = (*pfnwxCreateSoundBackendSDL)();
480 }
481 }
482 #endif
483 if (ms_backend && !ms_backend->IsAvailable())
484 {
485 wxDELETE(ms_backend);
486 }
487 }
488 #endif
489
490 #ifdef HAVE_SYS_SOUNDCARD_H
491 if (!ms_backend)
492 {
493 ms_backend = new wxSoundBackendOSS();
494 if (!ms_backend->IsAvailable())
495 {
496 wxDELETE(ms_backend);
497 }
498 }
499 #endif
500
501 if (!ms_backend)
502 ms_backend = new wxSoundBackendNull();
503
504 if (!ms_backend->HasNativeAsyncPlayback())
505 ms_backend = new wxSoundSyncOnlyAdaptor(ms_backend);
506
507 wxLogTrace(_T("sound"),
508 _T("using backend '%s'"), ms_backend->GetName().c_str());
509 }
510 }
511
512 /*static*/ void wxSound::UnloadBackend()
513 {
514 if (ms_backend)
515 {
516 wxLogTrace(_T("sound"), _T("unloading backend"));
517
518 Stop();
519
520 delete ms_backend;
521 ms_backend = NULL;
522 #if wxUSE_LIBSDL && wxUSE_PLUGINS
523 delete ms_backendSDL;
524 #endif
525 }
526 }
527
528 bool wxSound::DoPlay(unsigned flags) const
529 {
530 wxCHECK_MSG( IsOk(), false, _T("Attempt to play invalid wave data") );
531
532 EnsureBackend();
533 wxSoundPlaybackStatus status;
534 status.m_playing = true;
535 status.m_stopRequested = false;
536 return ms_backend->Play(m_data, flags, &status);
537 }
538
539 /*static*/ void wxSound::Stop()
540 {
541 if (ms_backend)
542 ms_backend->Stop();
543 }
544
545 /*static*/ bool wxSound::IsPlaying()
546 {
547 if (ms_backend)
548 return ms_backend->IsPlaying();
549 else
550 return false;
551 }
552
553 void wxSound::Free()
554 {
555 if (m_data)
556 m_data->DecRef();
557 }
558
559 typedef struct
560 {
561 wxUint32 uiSize;
562 wxUint16 uiFormatTag;
563 wxUint16 uiChannels;
564 wxUint32 ulSamplesPerSec;
565 wxUint32 ulAvgBytesPerSec;
566 wxUint16 uiBlockAlign;
567 wxUint16 uiBitsPerSample;
568 } WAVEFORMAT;
569
570 #define MONO 1 // and stereo is 2 by wav format
571 #define WAVE_FORMAT_PCM 1
572 #define WAVE_INDEX 8
573 #define FMT_INDEX 12
574
575 bool wxSound::LoadWAV(const wxUint8 *data, size_t length, bool copyData)
576 {
577 WAVEFORMAT waveformat;
578 wxUint32 ul;
579
580 if (length < 32 + sizeof(WAVEFORMAT))
581 return false;
582
583 memcpy(&waveformat, &data[FMT_INDEX + 4], sizeof(WAVEFORMAT));
584 waveformat.uiSize = wxUINT32_SWAP_ON_BE(waveformat.uiSize);
585 waveformat.uiFormatTag = wxUINT16_SWAP_ON_BE(waveformat.uiFormatTag);
586 waveformat.uiChannels = wxUINT16_SWAP_ON_BE(waveformat.uiChannels);
587 waveformat.ulSamplesPerSec = wxUINT32_SWAP_ON_BE(waveformat.ulSamplesPerSec);
588 waveformat.ulAvgBytesPerSec = wxUINT32_SWAP_ON_BE(waveformat.ulAvgBytesPerSec);
589 waveformat.uiBlockAlign = wxUINT16_SWAP_ON_BE(waveformat.uiBlockAlign);
590 waveformat.uiBitsPerSample = wxUINT16_SWAP_ON_BE(waveformat.uiBitsPerSample);
591
592 if (memcmp(data, "RIFF", 4) != 0)
593 return false;
594 if (memcmp(&data[WAVE_INDEX], "WAVE", 4) != 0)
595 return false;
596 if (memcmp(&data[FMT_INDEX], "fmt ", 4) != 0)
597 return false;
598 if (memcmp(&data[FMT_INDEX + waveformat.uiSize + 8], "data", 4) != 0)
599 return false;
600 memcpy(&ul,&data[FMT_INDEX + waveformat.uiSize + 12], 4);
601 ul = wxUINT32_SWAP_ON_BE(ul);
602
603 //WAS: if (ul + FMT_INDEX + waveformat.uiSize + 16 != length)
604 if (ul + FMT_INDEX + waveformat.uiSize + 16 > length)
605 return false;
606
607 if (waveformat.uiFormatTag != WAVE_FORMAT_PCM)
608 return false;
609
610 if (waveformat.ulSamplesPerSec !=
611 waveformat.ulAvgBytesPerSec / waveformat.uiBlockAlign)
612 return false;
613
614 m_data = new wxSoundData;
615 m_data->m_channels = waveformat.uiChannels;
616 m_data->m_samplingRate = waveformat.ulSamplesPerSec;
617 m_data->m_bitsPerSample = waveformat.uiBitsPerSample;
618 m_data->m_samples = ul / (m_data->m_channels * m_data->m_bitsPerSample / 8);
619 m_data->m_dataBytes = ul;
620
621 if (copyData)
622 {
623 m_data->m_dataWithHeader = new wxUint8[length];
624 memcpy(m_data->m_dataWithHeader, data, length);
625 }
626 else
627 m_data->m_dataWithHeader = (wxUint8*)data;
628
629 m_data->m_data =
630 (&m_data->m_dataWithHeader[FMT_INDEX + waveformat.uiSize + 8]);
631
632 return true;
633 }
634
635
636 // ----------------------------------------------------------------------------
637 // wxSoundCleanupModule
638 // ----------------------------------------------------------------------------
639
640 class wxSoundCleanupModule: public wxModule
641 {
642 public:
643 bool OnInit() { return true; }
644 void OnExit() { wxSound::UnloadBackend(); }
645 DECLARE_DYNAMIC_CLASS(wxSoundCleanupModule)
646 };
647
648 IMPLEMENT_DYNAMIC_CLASS(wxSoundCleanupModule, wxModule)
649
650 #endif