added support for async playback to Unix implementation of wxSound, implemented SDL...
[wxWidgets.git] / src / unix / sound_sdl.cpp
1 /////////////////////////////////////////////////////////////////////////////
2 // Name: sound_sdl.cpp
3 // Purpose: wxSound backend using SDL
4 // Author: Vaclav Slavik
5 // Modified by:
6 // Created: 2004/01/31
7 // RCS-ID: $Id$
8 // Copyright: (c) 2004, Vaclav Slavik
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_WAVE && wxUSE_LIBSDL
22
23 #include <SDL.h>
24
25 #ifndef WX_PRECOMP
26 #include "wx/event.h"
27 #include "wx/intl.h"
28 #include "wx/log.h"
29 #include "wx/list.h"
30 #include "wx/utils.h"
31 #endif
32
33 #include "wx/thread.h"
34 #include "wx/module.h"
35 #include "wx/sound.h"
36 #include "wx/listimpl.cpp"
37
38 // ----------------------------------------------------------------------------
39 // wxSoundBackendSDL, for Unix with libSDL
40 // ----------------------------------------------------------------------------
41
42 struct wxSoundBackendSDLQueueEntry
43 {
44 wxSoundData *m_data;
45 unsigned m_pos;
46 SDL_AudioSpec m_spec;
47 bool m_loop;
48 bool m_finished;
49 };
50
51 WX_DECLARE_LIST(wxSoundBackendSDLQueueEntry, wxSoundBackendSDLQueue);
52 WX_DEFINE_LIST(wxSoundBackendSDLQueue);
53
54
55 class wxSoundBackendSDLNotification : public wxEvent
56 {
57 public:
58 DECLARE_DYNAMIC_CLASS(wxSoundBackendSDLNotification)
59 wxSoundBackendSDLNotification();
60 wxEvent *Clone() const { return new wxSoundBackendSDLNotification(*this); }
61 };
62
63 typedef void (wxEvtHandler::*wxSoundBackendSDLNotificationFunction)
64 (wxSoundBackendSDLNotification&);
65
66 BEGIN_DECLARE_EVENT_TYPES()
67 DECLARE_LOCAL_EVENT_TYPE(wxEVT_SOUND_BACKEND_SDL_NOTIFICATION, -1)
68 END_DECLARE_EVENT_TYPES()
69
70 #define EVT_SOUND_BACKEND_SDL_NOTIFICATON(func) \
71 DECLARE_EVENT_TABLE_ENTRY(wxEVT_SOUND_BACKEND_SDL_NOTIFICATION, \
72 -1, \
73 -1, \
74 (wxObjectEventFunction) \
75 (wxSoundBackendSDLNotificationFunction)& func, \
76 (wxObject *) NULL ),
77
78 IMPLEMENT_DYNAMIC_CLASS(wxSoundBackendSDLNotification, wxEvtHandler)
79 DEFINE_EVENT_TYPE(wxEVT_SOUND_BACKEND_SDL_NOTIFICATION)
80
81 wxSoundBackendSDLNotification::wxSoundBackendSDLNotification()
82 {
83 SetEventType(wxEVT_SOUND_BACKEND_SDL_NOTIFICATION);
84 }
85
86 class wxSoundBackendSDLEvtHandler;
87
88 class wxSoundBackendSDL : public wxSoundBackend
89 {
90 public:
91 wxSoundBackendSDL()
92 : m_initialized(false), m_playing(false), m_evtHandler(NULL) {}
93 virtual ~wxSoundBackendSDL();
94
95 wxString GetName() const { return _T("Simple DirectMedia Layer"); }
96 int GetPriority() const { return 9; }
97 bool IsAvailable() const;
98 bool HasNativeAsyncPlayback() const { return true; }
99 bool Play(wxSoundData *data, unsigned flags);
100
101 void FillAudioBuffer(Uint8 *stream, int len);
102 bool PlayNextSampleInQueue();
103
104 private:
105 bool m_initialized;
106 bool m_playing;
107 wxSoundBackendSDLQueue m_queue;
108 wxSoundBackendSDLEvtHandler *m_evtHandler;
109 };
110
111 class wxSoundBackendSDLEvtHandler : public wxEvtHandler
112 {
113 public:
114 wxSoundBackendSDLEvtHandler(wxSoundBackendSDL *bk) : m_backend(bk) {}
115
116 private:
117 void OnNotify(wxSoundBackendSDLNotification& WXUNUSED(event))
118 {
119 wxLogTrace(_T("sound"),
120 _T("received playback status change notification"));
121 m_backend->PlayNextSampleInQueue();
122 }
123 wxSoundBackendSDL *m_backend;
124
125 DECLARE_EVENT_TABLE()
126 };
127
128 BEGIN_EVENT_TABLE(wxSoundBackendSDLEvtHandler, wxEvtHandler)
129 EVT_SOUND_BACKEND_SDL_NOTIFICATON(wxSoundBackendSDLEvtHandler::OnNotify)
130 END_EVENT_TABLE()
131
132 wxSoundBackendSDL::~wxSoundBackendSDL()
133 {
134 SDL_LockAudio();
135 if (m_playing)
136 SDL_CloseAudio();
137 SDL_UnlockAudio();
138 wxDELETE(m_evtHandler);
139 WX_CLEAR_LIST(wxSoundBackendSDLQueue, m_queue)
140 }
141
142 bool wxSoundBackendSDL::IsAvailable() const
143 {
144 if (m_initialized)
145 return true;
146 if (SDL_WasInit(SDL_INIT_AUDIO) != SDL_INIT_AUDIO)
147 {
148 if (SDL_Init(SDL_INIT_AUDIO | SDL_INIT_NOPARACHUTE) == -1)
149 return false;
150 }
151 wxConstCast(this, wxSoundBackendSDL)->m_initialized = true;
152 wxLogTrace(_T("sound"), _T("initialized SDL audio subsystem"));
153 return true;
154 }
155
156 extern "C" void wx_sdl_audio_callback(void *userdata, Uint8 *stream, int len)
157 {
158 wxSoundBackendSDL *bk = (wxSoundBackendSDL*)userdata;
159 bk->FillAudioBuffer(stream, len);
160 }
161
162 void wxSoundBackendSDL::FillAudioBuffer(Uint8 *stream, int len)
163 {
164 wxSoundBackendSDLQueueEntry *e = m_queue.front();
165 if (!e->m_finished)
166 {
167 // finished playing the sample
168 if (e->m_pos == e->m_data->m_dataBytes)
169 {
170 e->m_finished = true;
171 m_playing = false;
172 wxSoundBackendSDLNotification event;
173 m_evtHandler->AddPendingEvent(event);
174 }
175 // still something to play
176 else
177 {
178 unsigned size = ((len + e->m_pos) < e->m_data->m_dataBytes) ?
179 len :
180 (e->m_data->m_dataBytes - e->m_pos);
181 memcpy(stream, e->m_data->m_data + e->m_pos, size);
182 e->m_pos += size;
183 len -= size;
184 stream += size;
185 }
186 }
187 // the sample doesn't play, fill the buffer with silence and wait for
188 // the main thread to shut the playback down:
189 if (len > 0)
190 {
191 if (e->m_loop)
192 {
193 e->m_pos = 0;
194 FillAudioBuffer(stream, len);
195 return;
196 }
197 else
198 {
199 memset(stream, e->m_spec.silence, len);
200 }
201 }
202 }
203
204 bool wxSoundBackendSDL::Play(wxSoundData *data, unsigned flags)
205 {
206 data->IncRef();
207
208 wxSoundBackendSDLQueueEntry *e = new wxSoundBackendSDLQueueEntry();
209 e->m_data = data;
210 e->m_pos = 0;
211 e->m_loop = (flags & wxSOUND_LOOP);
212 e->m_finished = false;
213 e->m_spec.freq = data->m_samplingRate;
214 e->m_spec.channels = data->m_channels;
215 e->m_spec.silence = 0;
216 e->m_spec.samples = 4096;
217 e->m_spec.size = 0;
218 e->m_spec.callback = wx_sdl_audio_callback;
219 e->m_spec.userdata = (void*)this;
220
221 if (data->m_bitsPerSample == 8)
222 e->m_spec.format = AUDIO_U8;
223 else if (data->m_bitsPerSample == 16)
224 e->m_spec.format = AUDIO_S16LSB;
225 else
226 return false;
227
228 m_queue.push_back(e);
229 wxLogTrace(_T("sound"), _T("queued sample %p for playback"), e);
230
231 if (!PlayNextSampleInQueue())
232 return false;
233
234 if (!(flags & wxSOUND_ASYNC))
235 {
236 wxLogTrace(_T("sound"), _T("waiting for sample to finish"));
237 while (!m_queue.empty() && m_queue.front() == e && !e->m_finished)
238 {
239 #if wxUSE_THREADS
240 // give the playback thread a chance to add event to pending
241 // events queue, release GUI lock temporarily:
242 if (wxThread::IsMain())
243 wxMutexGuiLeave();
244 #endif
245 wxUsleep(10);
246 #if wxUSE_THREADS
247 if (wxThread::IsMain())
248 wxMutexGuiEnter();
249 #endif
250 }
251 wxLogTrace(_T("sound"), _T("sample finished"));
252 }
253
254 return true;
255 }
256
257 bool wxSoundBackendSDL::PlayNextSampleInQueue()
258 {
259 bool status = true;
260
261 SDL_LockAudio();
262
263 if (!m_evtHandler)
264 m_evtHandler = new wxSoundBackendSDLEvtHandler(this);
265
266 if (!m_playing && !m_queue.empty())
267 {
268 bool needsReopen = true;
269 // shut down playing of finished sound:
270 wxSoundBackendSDLQueueEntry *e = m_queue.front();
271 if (e->m_finished)
272 {
273 SDL_PauseAudio(1);
274 e->m_data->DecRef();
275 m_queue.pop_front();
276 if (!m_queue.empty() &&
277 e->m_spec.freq == m_queue.front()->m_spec.freq &&
278 e->m_spec.channels == m_queue.front()->m_spec.channels &&
279 e->m_spec.format == m_queue.front()->m_spec.format)
280 {
281 needsReopen = false;
282 }
283 else
284 {
285 SDL_CloseAudio();
286 wxLogTrace(_T("sound"), _T("closed audio"));
287 }
288 delete e;
289 }
290 // start playing another one:
291 if (!m_queue.empty())
292 {
293 wxSoundBackendSDLQueueEntry *e = m_queue.front();
294 m_playing = true;
295 wxLogTrace(_T("sound"), _T("playing sample %p"), e);
296
297 if (needsReopen)
298 {
299 wxLogTrace(_T("sound"), _T("opening SDL audio..."));
300 status = (SDL_OpenAudio(&e->m_spec, NULL) >= 0);
301 if (status)
302 {
303 #if wxUSE_LOG_DEBUG
304 char driver[256];
305 SDL_AudioDriverName(driver, 256);
306 wxLogTrace(_T("sound"), _T("opened audio, driver '%s'"),
307 wxString(driver, wxConvLocal).c_str());
308 #endif
309 SDL_PauseAudio(0);
310 }
311 else
312 {
313 wxString err(SDL_GetError(), wxConvLocal);
314 wxLogError(_("Couldn't open audio: %s"), err.c_str());
315 m_queue.pop_front();
316 delete e;
317 }
318 }
319 else
320 SDL_PauseAudio(0);
321 }
322 }
323
324 SDL_UnlockAudio();
325
326 return status;
327 }
328
329 extern "C" wxSoundBackend *wxCreateSoundBackendSDL()
330 {
331 return new wxSoundBackendSDL();
332 }
333
334 #endif // wxUSE_WAVE && wxUSE_LIBSDL