///////////////////////////////////////////////////////////////////////////// // Name: sound_sdl.cpp // Purpose: wxSound backend using SDL // Author: Vaclav Slavik // Modified by: // Created: 2004/01/31 // RCS-ID: $Id$ // Copyright: (c) 2004, Vaclav Slavik // Licence: wxWindows licence ///////////////////////////////////////////////////////////////////////////// // for compilers that support precompilation, includes "wx.h". #include "wx/wxprec.h" #include "wx/setup.h" #if defined(__BORLANDC__) #pragma hdrstop #endif #if wxUSE_WAVE && wxUSE_LIBSDL #include #ifndef WX_PRECOMP #include "wx/event.h" #include "wx/intl.h" #include "wx/log.h" #include "wx/list.h" #include "wx/utils.h" #endif #include "wx/thread.h" #include "wx/module.h" #include "wx/sound.h" #include "wx/listimpl.cpp" // ---------------------------------------------------------------------------- // wxSoundBackendSDL, for Unix with libSDL // ---------------------------------------------------------------------------- struct wxSoundBackendSDLQueueEntry { wxSoundData *m_data; unsigned m_pos; SDL_AudioSpec m_spec; bool m_loop; bool m_finished; }; WX_DECLARE_LIST(wxSoundBackendSDLQueueEntry, wxSoundBackendSDLQueue); WX_DEFINE_LIST(wxSoundBackendSDLQueue); class wxSoundBackendSDLNotification : public wxEvent { public: DECLARE_DYNAMIC_CLASS(wxSoundBackendSDLNotification) wxSoundBackendSDLNotification(); wxEvent *Clone() const { return new wxSoundBackendSDLNotification(*this); } }; typedef void (wxEvtHandler::*wxSoundBackendSDLNotificationFunction) (wxSoundBackendSDLNotification&); BEGIN_DECLARE_EVENT_TYPES() DECLARE_LOCAL_EVENT_TYPE(wxEVT_SOUND_BACKEND_SDL_NOTIFICATION, -1) END_DECLARE_EVENT_TYPES() #define EVT_SOUND_BACKEND_SDL_NOTIFICATON(func) \ DECLARE_EVENT_TABLE_ENTRY(wxEVT_SOUND_BACKEND_SDL_NOTIFICATION, \ -1, \ -1, \ (wxObjectEventFunction) \ (wxSoundBackendSDLNotificationFunction)& func, \ (wxObject *) NULL ), IMPLEMENT_DYNAMIC_CLASS(wxSoundBackendSDLNotification, wxEvtHandler) DEFINE_EVENT_TYPE(wxEVT_SOUND_BACKEND_SDL_NOTIFICATION) wxSoundBackendSDLNotification::wxSoundBackendSDLNotification() { SetEventType(wxEVT_SOUND_BACKEND_SDL_NOTIFICATION); } class wxSoundBackendSDLEvtHandler; class wxSoundBackendSDL : public wxSoundBackend { public: wxSoundBackendSDL() : m_initialized(false), m_playing(false), m_evtHandler(NULL) {} virtual ~wxSoundBackendSDL(); wxString GetName() const { return _T("Simple DirectMedia Layer"); } int GetPriority() const { return 9; } bool IsAvailable() const; bool HasNativeAsyncPlayback() const { return true; } bool Play(wxSoundData *data, unsigned flags); void FillAudioBuffer(Uint8 *stream, int len); bool PlayNextSampleInQueue(); private: bool m_initialized; bool m_playing; wxSoundBackendSDLQueue m_queue; wxSoundBackendSDLEvtHandler *m_evtHandler; }; class wxSoundBackendSDLEvtHandler : public wxEvtHandler { public: wxSoundBackendSDLEvtHandler(wxSoundBackendSDL *bk) : m_backend(bk) {} private: void OnNotify(wxSoundBackendSDLNotification& WXUNUSED(event)) { wxLogTrace(_T("sound"), _T("received playback status change notification")); m_backend->PlayNextSampleInQueue(); } wxSoundBackendSDL *m_backend; DECLARE_EVENT_TABLE() }; BEGIN_EVENT_TABLE(wxSoundBackendSDLEvtHandler, wxEvtHandler) EVT_SOUND_BACKEND_SDL_NOTIFICATON(wxSoundBackendSDLEvtHandler::OnNotify) END_EVENT_TABLE() wxSoundBackendSDL::~wxSoundBackendSDL() { SDL_LockAudio(); if (m_playing) SDL_CloseAudio(); SDL_UnlockAudio(); wxDELETE(m_evtHandler); WX_CLEAR_LIST(wxSoundBackendSDLQueue, m_queue) } bool wxSoundBackendSDL::IsAvailable() const { if (m_initialized) return true; if (SDL_WasInit(SDL_INIT_AUDIO) != SDL_INIT_AUDIO) { if (SDL_Init(SDL_INIT_AUDIO | SDL_INIT_NOPARACHUTE) == -1) return false; } wxConstCast(this, wxSoundBackendSDL)->m_initialized = true; wxLogTrace(_T("sound"), _T("initialized SDL audio subsystem")); return true; } extern "C" void wx_sdl_audio_callback(void *userdata, Uint8 *stream, int len) { wxSoundBackendSDL *bk = (wxSoundBackendSDL*)userdata; bk->FillAudioBuffer(stream, len); } void wxSoundBackendSDL::FillAudioBuffer(Uint8 *stream, int len) { wxSoundBackendSDLQueueEntry *e = m_queue.front(); if (!e->m_finished) { // finished playing the sample if (e->m_pos == e->m_data->m_dataBytes) { e->m_finished = true; m_playing = false; wxSoundBackendSDLNotification event; m_evtHandler->AddPendingEvent(event); } // still something to play else { unsigned size = ((len + e->m_pos) < e->m_data->m_dataBytes) ? len : (e->m_data->m_dataBytes - e->m_pos); memcpy(stream, e->m_data->m_data + e->m_pos, size); e->m_pos += size; len -= size; stream += size; } } // the sample doesn't play, fill the buffer with silence and wait for // the main thread to shut the playback down: if (len > 0) { if (e->m_loop) { e->m_pos = 0; FillAudioBuffer(stream, len); return; } else { memset(stream, e->m_spec.silence, len); } } } bool wxSoundBackendSDL::Play(wxSoundData *data, unsigned flags) { data->IncRef(); wxSoundBackendSDLQueueEntry *e = new wxSoundBackendSDLQueueEntry(); e->m_data = data; e->m_pos = 0; e->m_loop = (flags & wxSOUND_LOOP); e->m_finished = false; e->m_spec.freq = data->m_samplingRate; e->m_spec.channels = data->m_channels; e->m_spec.silence = 0; e->m_spec.samples = 4096; e->m_spec.size = 0; e->m_spec.callback = wx_sdl_audio_callback; e->m_spec.userdata = (void*)this; if (data->m_bitsPerSample == 8) e->m_spec.format = AUDIO_U8; else if (data->m_bitsPerSample == 16) e->m_spec.format = AUDIO_S16LSB; else return false; m_queue.push_back(e); wxLogTrace(_T("sound"), _T("queued sample %p for playback"), e); if (!PlayNextSampleInQueue()) return false; if (!(flags & wxSOUND_ASYNC)) { wxLogTrace(_T("sound"), _T("waiting for sample to finish")); while (!m_queue.empty() && m_queue.front() == e && !e->m_finished) { #if wxUSE_THREADS // give the playback thread a chance to add event to pending // events queue, release GUI lock temporarily: if (wxThread::IsMain()) wxMutexGuiLeave(); #endif wxUsleep(10); #if wxUSE_THREADS if (wxThread::IsMain()) wxMutexGuiEnter(); #endif } wxLogTrace(_T("sound"), _T("sample finished")); } return true; } bool wxSoundBackendSDL::PlayNextSampleInQueue() { bool status = true; SDL_LockAudio(); if (!m_evtHandler) m_evtHandler = new wxSoundBackendSDLEvtHandler(this); if (!m_playing && !m_queue.empty()) { bool needsReopen = true; // shut down playing of finished sound: wxSoundBackendSDLQueueEntry *e = m_queue.front(); if (e->m_finished) { SDL_PauseAudio(1); e->m_data->DecRef(); m_queue.pop_front(); if (!m_queue.empty() && e->m_spec.freq == m_queue.front()->m_spec.freq && e->m_spec.channels == m_queue.front()->m_spec.channels && e->m_spec.format == m_queue.front()->m_spec.format) { needsReopen = false; } else { SDL_CloseAudio(); wxLogTrace(_T("sound"), _T("closed audio")); } delete e; } // start playing another one: if (!m_queue.empty()) { wxSoundBackendSDLQueueEntry *e = m_queue.front(); m_playing = true; wxLogTrace(_T("sound"), _T("playing sample %p"), e); if (needsReopen) { wxLogTrace(_T("sound"), _T("opening SDL audio...")); status = (SDL_OpenAudio(&e->m_spec, NULL) >= 0); if (status) { #if wxUSE_LOG_DEBUG char driver[256]; SDL_AudioDriverName(driver, 256); wxLogTrace(_T("sound"), _T("opened audio, driver '%s'"), wxString(driver, wxConvLocal).c_str()); #endif SDL_PauseAudio(0); } else { wxString err(SDL_GetError(), wxConvLocal); wxLogError(_("Couldn't open audio: %s"), err.c_str()); m_queue.pop_front(); delete e; } } else SDL_PauseAudio(0); } } SDL_UnlockAudio(); return status; } extern "C" wxSoundBackend *wxCreateSoundBackendSDL() { return new wxSoundBackendSDL(); } #endif // wxUSE_WAVE && wxUSE_LIBSDL