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