added support for async playback to Unix implementation of wxSound, implemented SDL...
[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, Vaclav Slavik
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_WAVE
27
28 #include <stdio.h>
29 #include <unistd.h>
30 #include <fcntl.h>
31 #include <sys/ioctl.h>
32
33 #if 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 // wxSoundBackendNull, used in absence of audio API or card
51 // ----------------------------------------------------------------------------
52
53 class wxSoundBackendNull : public wxSoundBackend
54 {
55 public:
56 wxString GetName() const { return _("No sound"); }
57 int GetPriority() const { return 0; }
58 bool IsAvailable() const { return true; }
59 bool HasNativeAsyncPlayback() const { return true; }
60 bool Play(wxSoundData *WXUNUSED(data), unsigned WXUNUSED(flags))
61 { return true; }
62 };
63
64
65 // ----------------------------------------------------------------------------
66 // wxSoundBackendOSS, for Linux
67 // ----------------------------------------------------------------------------
68
69 #ifdef HAVE_SYS_SOUNDCARD_H
70
71 #ifndef AUDIODEV
72 #define AUDIODEV "/dev/dsp" // Default path for audio device
73 #endif
74
75 class wxSoundBackendOSS : public wxSoundBackend
76 {
77 public:
78 wxString GetName() const { return _T("Open Sound System"); }
79 int GetPriority() const { return 10; }
80 bool IsAvailable() const;
81 bool HasNativeAsyncPlayback() const { return false; }
82 bool Play(wxSoundData *data, unsigned flags);
83
84 private:
85 int OpenDSP(const wxSoundData *data);
86 bool InitDSP(int dev, int iDataBits, int iChannel,
87 unsigned long ulSamplingRate);
88
89 int m_DSPblkSize; // Size of the DSP buffer
90 };
91
92 bool wxSoundBackendOSS::IsAvailable() const
93 {
94 int fd;
95 fd = open(AUDIODEV, O_WRONLY | O_NONBLOCK);
96 if (fd < 0)
97 return false;
98 close(fd);
99 return true;
100 }
101
102 bool wxSoundBackendOSS::Play(wxSoundData *data, unsigned flags)
103 {
104 int dev = OpenDSP(data);
105
106 if (dev < 0)
107 return false;
108
109 ioctl(dev, SNDCTL_DSP_SYNC, 0);
110
111 do
112 {
113 bool play = true;
114 int i;
115 unsigned l = 0;
116 size_t datasize = data->m_dataBytes;
117
118 do
119 {
120 i= (int)((l + m_DSPblkSize) < datasize ?
121 m_DSPblkSize : (datasize - l));
122 if (write(dev, &data->m_data[l], i) != i)
123 {
124 play = false;
125 }
126 l += i;
127 } while (play && l < datasize);
128 } while (flags & wxSOUND_LOOP);
129
130 close(dev);
131 return true;
132 }
133
134 int wxSoundBackendOSS::OpenDSP(const wxSoundData *data)
135 {
136 int dev = -1;
137
138 if ((dev = open(AUDIODEV, O_WRONLY, 0)) <0)
139 return -1;
140
141 if (!InitDSP(dev,
142 (int)data->m_bitsPerSample,
143 data->m_channels == 1 ? 0 : 1,
144 data->m_samplingRate))
145 {
146 close(dev);
147 return -1;
148 }
149
150 return dev;
151 }
152
153 bool wxSoundBackendOSS::InitDSP(int dev, int iDataBits, int iChannel,
154 unsigned long ulSamplingRate)
155 {
156 if (ioctl(dev, SNDCTL_DSP_GETBLKSIZE, &m_DSPblkSize) < 0)
157 return false;
158 if (m_DSPblkSize < 4096 || m_DSPblkSize > 65536)
159 return false;
160 if (ioctl(dev, SNDCTL_DSP_SAMPLESIZE, &iDataBits) < 0)
161 return false;
162 if (ioctl(dev, SNDCTL_DSP_STEREO, &iChannel) < 0)
163 return false;
164 if (ioctl(dev, SNDCTL_DSP_SPEED, &ulSamplingRate) < 0)
165 return false;
166 return true;
167 }
168
169 #endif // HAVE_SYS_SOUNDCARD_H
170
171
172 // ----------------------------------------------------------------------------
173 // wxSoundData
174 // ----------------------------------------------------------------------------
175
176 void wxSoundData::IncRef()
177 {
178 m_refCnt++;
179 }
180
181 void wxSoundData::DecRef()
182 {
183 if (--m_refCnt == 0)
184 delete this;
185 }
186
187 wxSoundData::~wxSoundData()
188 {
189 delete[] m_dataWithHeader;
190 }
191
192
193 // ----------------------------------------------------------------------------
194 // wxSoundAsyncPlaybackThread
195 // ----------------------------------------------------------------------------
196
197 #if wxUSE_THREADS
198
199 // mutex for all wxSound's synchronization
200 static wxMutex gs_soundMutex;
201
202 // this class manages asynchronous playback of audio if the backend doesn't
203 // support it natively (e.g. OSS backend)
204 class wxSoundAsyncPlaybackThread : public wxThread
205 {
206 public:
207 wxSoundAsyncPlaybackThread(wxSoundBackend *backend,
208 wxSoundData *data, unsigned flags)
209 : wxThread(), m_backend(backend), m_data(data), m_flags(flags) {}
210 virtual ExitCode Entry()
211 {
212 m_backend->Play(m_data, m_flags & ~wxSOUND_ASYNC);
213 wxMutexLocker locker(gs_soundMutex);
214 m_data->DecRef();
215 wxLogTrace(_T("sound"), _T("terminated async playback thread"));
216 return 0;
217 }
218
219 protected:
220 wxSoundBackend *m_backend;
221 wxSoundData *m_data;
222 unsigned m_flags;
223 };
224
225 #endif // wxUSE_THREADS
226
227 // ----------------------------------------------------------------------------
228 // wxSound
229 // ----------------------------------------------------------------------------
230
231 wxSoundBackend *wxSound::ms_backend = NULL;
232
233 // FIXME - temporary, until we have plugins architecture
234 #if wxUSE_LIBSDL
235 #if wxUSE_PLUGINS
236 wxDynamicLibrary *wxSound::ms_backendSDL = NULL;
237 #else
238 extern "C" wxSoundBackend *wxCreateSoundBackendSDL();
239 #endif
240 #endif
241
242 wxSound::wxSound() : m_data(NULL)
243 {
244 }
245
246 wxSound::wxSound(const wxString& sFileName, bool isResource) : m_data(NULL)
247 {
248 Create(sFileName, isResource);
249 }
250
251 wxSound::wxSound(int size, const wxByte* data) : m_data(NULL)
252 {
253 Create(size, data);
254 }
255
256 wxSound::~wxSound()
257 {
258 Free();
259 }
260
261 bool wxSound::Create(const wxString& fileName, bool isResource)
262 {
263 wxASSERT_MSG( !isResource,
264 _T("Loading sound from resources is only supported on Windows") );
265
266 Free();
267
268 wxFile fileWave;
269 if (!fileWave.Open(fileName, wxFile::read))
270 {
271 return false;
272 }
273
274 size_t len = fileWave.Length();
275 wxUint8 *data = new wxUint8[len];
276 if (fileWave.Read(data, len) != len)
277 {
278 wxLogError(_("Couldn't load sound data from '%s'."), fileName.c_str());
279 return false;
280 }
281
282 if (!LoadWAV(data, len, false))
283 {
284 wxLogError(_("Sound file '%s' is in unsupported format."),
285 fileName.c_str());
286 return false;
287 }
288
289 return true;
290 }
291
292 bool wxSound::Create(int size, const wxByte* data)
293 {
294 wxASSERT( data != NULL );
295
296 Free();
297 if (!LoadWAV(data, size, true))
298 {
299 wxLogError(_("Sound data are in unsupported format."));
300 return false;
301 }
302 return true;
303 }
304
305 /*static*/ void wxSound::EnsureBackend()
306 {
307 if (!ms_backend)
308 {
309 // FIXME -- make this fully dynamic when plugins architecture is in
310 // place
311 #ifdef HAVE_SYS_SOUNDCARD_H
312 ms_backend = new wxSoundBackendOSS();
313 if (!ms_backend->IsAvailable())
314 {
315 wxDELETE(ms_backend);
316 }
317 #endif
318
319 #if wxUSE_LIBSDL
320 if (!ms_backend)
321 {
322 #if !wxUSE_PLUGINS
323 ms_backend = wxCreateSoundBackendSDL();
324 #else
325 wxString dllname;
326 dllname.Printf(_T("%s/%s"),
327 wxDynamicLibrary::GetPluginsDirectory().c_str(),
328 wxDynamicLibrary::CanonicalizePluginName(
329 _T("sound_sdl"), wxDL_PLUGIN_BASE).c_str());
330 wxLogTrace(_T("sound"),
331 _T("trying to load SDL plugin from '%s'..."),
332 dllname.c_str());
333 wxLogNull null;
334 ms_backendSDL = new wxDynamicLibrary(dllname, wxDL_NOW);
335 if (!ms_backendSDL->IsLoaded())
336 {
337 wxDELETE(ms_backendSDL);
338 }
339 else
340 {
341 typedef wxSoundBackend *(*wxCreateSoundBackend_t)();
342 wxDYNLIB_FUNCTION(wxCreateSoundBackend_t,
343 wxCreateSoundBackendSDL, *ms_backendSDL);
344 if (pfnwxCreateSoundBackendSDL)
345 {
346 ms_backend = (*pfnwxCreateSoundBackendSDL)();
347 }
348 }
349 #endif
350 if (ms_backend && !ms_backend->IsAvailable())
351 {
352 wxDELETE(ms_backend);
353 }
354 }
355 #endif
356
357 if (!ms_backend)
358 ms_backend = new wxSoundBackendNull();
359
360 wxLogTrace(_T("sound"),
361 _T("using backend '%s'"), ms_backend->GetName().c_str());
362 }
363 }
364
365 /*static*/ void wxSound::UnloadBackend()
366 {
367 if (ms_backend)
368 {
369 wxLogTrace(_T("sound"), _T("unloading backend"));
370 delete ms_backend;
371 ms_backend = NULL;
372 #if wxUSE_LIBSDL && wxUSE_PLUGINS
373 delete ms_backendSDL;
374 #endif
375 }
376 }
377
378 bool wxSound::DoPlay(unsigned flags)
379 {
380 wxCHECK_MSG( IsOk(), false, _T("Attempt to play invalid wave data") );
381
382 EnsureBackend();
383
384 if ((flags & wxSOUND_ASYNC) && !ms_backend->HasNativeAsyncPlayback())
385 {
386 #if wxUSE_THREADS
387 wxMutexLocker locker(gs_soundMutex);
388 m_data->IncRef();
389 wxThread *th = new wxSoundAsyncPlaybackThread(ms_backend, m_data, flags);
390 th->Create();
391 th->Run();
392 wxLogTrace(_T("sound"), _T("launched async playback thread"));
393 #else
394 wxLogError(_("Unable to play sound asynchronously."));
395 return false;
396 #endif
397 }
398 else
399 {
400 ms_backend->Play(m_data, flags);
401 }
402 return true;
403 }
404
405 void wxSound::Free()
406 {
407 #if wxUSE_THREADS
408 wxMutexLocker locker(gs_soundMutex);
409 #endif
410 if (m_data)
411 m_data->DecRef();
412 }
413
414 typedef struct
415 {
416 wxUint32 uiSize;
417 wxUint16 uiFormatTag;
418 wxUint16 uiChannels;
419 wxUint32 ulSamplesPerSec;
420 wxUint32 ulAvgBytesPerSec;
421 wxUint16 uiBlockAlign;
422 wxUint16 uiBitsPerSample;
423 } WAVEFORMAT;
424
425 #define MONO 1 // and stereo is 2 by wav format
426 #define WAVE_FORMAT_PCM 1
427 #define WAVE_INDEX 8
428 #define FMT_INDEX 12
429
430 bool wxSound::LoadWAV(const wxUint8 *data, size_t length, bool copyData)
431 {
432 WAVEFORMAT waveformat;
433 wxUint32 ul;
434
435 if (length < 32 + sizeof(WAVEFORMAT))
436 return false;
437
438 memcpy(&waveformat, &data[FMT_INDEX + 4], sizeof(WAVEFORMAT));
439 waveformat.uiSize = wxUINT32_SWAP_ON_BE(waveformat.uiSize);
440 waveformat.uiFormatTag = wxUINT16_SWAP_ON_BE(waveformat.uiFormatTag);
441 waveformat.uiChannels = wxUINT16_SWAP_ON_BE(waveformat.uiChannels);
442 waveformat.ulSamplesPerSec = wxUINT32_SWAP_ON_BE(waveformat.ulSamplesPerSec);
443 waveformat.ulAvgBytesPerSec = wxUINT32_SWAP_ON_BE(waveformat.ulAvgBytesPerSec);
444 waveformat.uiBlockAlign = wxUINT16_SWAP_ON_BE(waveformat.uiBlockAlign);
445 waveformat.uiBitsPerSample = wxUINT16_SWAP_ON_BE(waveformat.uiBitsPerSample);
446
447 if (memcmp(data, "RIFF", 4) != 0)
448 return false;
449 if (memcmp(&data[WAVE_INDEX], "WAVE", 4) != 0)
450 return false;
451 if (memcmp(&data[FMT_INDEX], "fmt ", 4) != 0)
452 return false;
453 if (memcmp(&data[FMT_INDEX + waveformat.uiSize + 8], "data", 4) != 0)
454 return false;
455 memcpy(&ul,&data[FMT_INDEX + waveformat.uiSize + 12], 4);
456 ul = wxUINT32_SWAP_ON_BE(ul);
457
458 //WAS: if (ul + FMT_INDEX + waveformat.uiSize + 16 != length)
459 if (ul + FMT_INDEX + waveformat.uiSize + 16 > length)
460 return false;
461
462 if (waveformat.uiFormatTag != WAVE_FORMAT_PCM)
463 return false;
464
465 if (waveformat.ulSamplesPerSec !=
466 waveformat.ulAvgBytesPerSec / waveformat.uiBlockAlign)
467 return false;
468
469 m_data = new wxSoundData;
470 m_data->m_channels = waveformat.uiChannels;
471 m_data->m_samplingRate = waveformat.ulSamplesPerSec;
472 m_data->m_bitsPerSample = waveformat.uiBitsPerSample;
473 m_data->m_samples = ul / (m_data->m_channels * m_data->m_bitsPerSample / 8);
474 m_data->m_dataBytes = ul;
475
476 if (copyData)
477 {
478 m_data->m_dataWithHeader = new wxUint8[length];
479 memcpy(m_data->m_dataWithHeader, data, length);
480 }
481 else
482 m_data->m_dataWithHeader = (wxUint8*)data;
483
484 m_data->m_data =
485 (&m_data->m_dataWithHeader[FMT_INDEX + waveformat.uiSize + 8]);
486
487 return true;
488 }
489
490
491 // ----------------------------------------------------------------------------
492 // wxSoundCleanupModule
493 // ----------------------------------------------------------------------------
494
495 class wxSoundCleanupModule: public wxModule
496 {
497 public:
498 bool OnInit() { return true; }
499 void OnExit() { wxSound::UnloadBackend(); }
500 DECLARE_DYNAMIC_CLASS(wxSoundCleanupModule)
501 };
502
503 IMPLEMENT_DYNAMIC_CLASS(wxSoundCleanupModule, wxModule)
504
505 #endif