From 7866fade02b507f76971beb74f63707ac5ba18c6 Mon Sep 17 00:00:00 2001 From: Glenn Rice Date: Tue, 17 Aug 2010 02:14:04 +0000 Subject: [PATCH] Add volume control for the pulse audio backend. Unfortunately that can not be done with the pulse-simple api, so I had to switch to the asynchronous pulse api. git-svn-id: https://dolphin-emu.googlecode.com/svn/trunk@6104 8ced0084-cf51-0410-be5f-012b33b47a6e --- SConstruct | 2 +- .../Core/AudioCommon/Src/PulseAudioStream.cpp | 261 ++++++++++++++++-- .../Core/AudioCommon/Src/PulseAudioStream.h | 33 +-- .../Plugins/Plugin_DSP_HLE/Src/ConfigDlg.cpp | 47 ++-- Source/Plugins/Plugin_DSP_HLE/Src/ConfigDlg.h | 7 +- .../Plugin_DSP_LLE/Src/DSPConfigDlgLLE.cpp | 7 +- 6 files changed, 289 insertions(+), 68 deletions(-) diff --git a/SConstruct b/SConstruct index e19c325aa7..d2538b7108 100644 --- a/SConstruct +++ b/SConstruct @@ -247,7 +247,7 @@ else: conf.Define('HAVE_OPENAL', env['HAVE_OPENAL']) env['HAVE_PORTAUDIO'] = conf.CheckPortaudio(1890) conf.Define('HAVE_PORTAUDIO', env['HAVE_PORTAUDIO']) - env['HAVE_PULSEAUDIO'] = conf.CheckPKG('libpulse-simple') + env['HAVE_PULSEAUDIO'] = conf.CheckPKG('libpulse') conf.Define('HAVE_PULSEAUDIO', env['HAVE_PULSEAUDIO']) env['HAVE_X11'] = conf.CheckPKG('x11') diff --git a/Source/Core/AudioCommon/Src/PulseAudioStream.cpp b/Source/Core/AudioCommon/Src/PulseAudioStream.cpp index 321c502b9b..afad3a31b1 100644 --- a/Source/Core/AudioCommon/Src/PulseAudioStream.cpp +++ b/Source/Core/AudioCommon/Src/PulseAudioStream.cpp @@ -21,9 +21,11 @@ #include "PulseAudioStream.h" #define BUFFER_SIZE 4096 -#define BUFFER_SIZE_BYTES (BUFFER_SIZE*2*2) +#define BUFFER_SIZE_BYTES (BUFFER_SIZE * 4) -PulseAudio::PulseAudio(CMixer *mixer) : SoundStream(mixer), thread_data(0), handle(NULL) +PulseAudio::PulseAudio(CMixer *mixer) + : SoundStream(mixer), thread_running(false), mainloop(NULL) + , context(NULL), stream(NULL), iVolume(100) { mix_buffer = new u8[BUFFER_SIZE_BYTES]; } @@ -33,22 +35,22 @@ PulseAudio::~PulseAudio() delete [] mix_buffer; } -static void *ThreadTrampoline(void *args) +void *PulseAudio::ThreadTrampoline(void *args) { - reinterpret_cast(args)->SoundLoop(); + ((PulseAudio *)args)->SoundLoop(); return NULL; } bool PulseAudio::Start() { + thread_running = true; thread = new Common::Thread(&ThreadTrampoline, this); - thread_data = 0; return true; } void PulseAudio::Stop() { - thread_data = 1; + thread_running = false; delete thread; thread = NULL; } @@ -61,22 +63,16 @@ void PulseAudio::Update() // Called on audio thread. void PulseAudio::SoundLoop() { - if (!PulseInit()) { - thread_data = 2; - return; - } - while (!thread_data) + thread_running = PulseInit(); + + while (thread_running) { - int err; int frames_to_deliver = 512; - m_mixer->Mix(reinterpret_cast(mix_buffer), frames_to_deliver); - if (pa_simple_write(handle, mix_buffer, frames_to_deliver * 2 * 2, &err) < 0) - { - ERROR_LOG(AUDIO, "pa_simple_write fail: %s", pa_strerror(err)); - } + m_mixer->Mix((short *)mix_buffer, frames_to_deliver); + if (!Write(mix_buffer, frames_to_deliver * 4)) + ERROR_LOG(AUDIO, "PulseAudio failure writing data"); } PulseShutdown(); - thread_data = 2; } bool PulseAudio::PulseInit() @@ -88,25 +84,238 @@ bool PulseAudio::PulseInit() 2 }; - int err; + mainloop = pa_threaded_mainloop_new(); - if (!(handle = pa_simple_new(NULL, "dolphin-emu", PA_STREAM_PLAYBACK, NULL, - "emulator", &ss, NULL, NULL, &err))) + context = pa_context_new(pa_threaded_mainloop_get_api(mainloop), "dolphin-emu"); + pa_context_set_state_callback(context, ContextStateCB, this); + + if (pa_context_connect(context, NULL, PA_CONTEXT_NOFLAGS, NULL) < 0) { - ERROR_LOG(AUDIO, "PulseAudio open error: %s\n", pa_strerror(err)); + ERROR_LOG(AUDIO, "PulseAudio failed to connect context: %s", + pa_strerror(pa_context_errno(context))); return false; } - NOTICE_LOG(AUDIO, "Pulse successfully initialized.\n"); + pa_threaded_mainloop_lock(mainloop); + pa_threaded_mainloop_start(mainloop); + + for (;;) + { + pa_context_state_t state; + + state = pa_context_get_state(context); + + if (state == PA_CONTEXT_READY) + break; + + if (!PA_CONTEXT_IS_GOOD(state)) + { + ERROR_LOG(AUDIO, "PulseAudio context state failure: %s", + pa_strerror(pa_context_errno(context))); + pa_threaded_mainloop_unlock(mainloop); + return false; + } + + // Wait until the context is ready + pa_threaded_mainloop_wait(mainloop); + } + + if (!(stream = pa_stream_new(context, "emulator", &ss, NULL))) + { + ERROR_LOG(AUDIO, "PulseAudio failed to create playback stream: %s", + pa_strerror(pa_context_errno(context))); + pa_threaded_mainloop_unlock(mainloop); + return false; + } + + // Set callbacks for the playback stream + pa_stream_set_state_callback(stream, StreamStateCB, this); + pa_stream_set_write_callback(stream, StreamWriteCB, this); + + if (pa_stream_connect_playback(stream, NULL, NULL, PA_STREAM_NOFLAGS, NULL, NULL) < 0) + { + ERROR_LOG(AUDIO, "PulseAudio failed to connect playback stream: %s", + pa_strerror(pa_context_errno(context))); + pa_threaded_mainloop_unlock(mainloop); + return false; + } + + for (;;) + { + pa_stream_state_t state; + + state = pa_stream_get_state(stream); + + if (state == PA_STREAM_READY) + break; + + if (!PA_STREAM_IS_GOOD(state)) + { + ERROR_LOG(AUDIO, "PulseAudio stream state failure: %s", + pa_strerror(pa_context_errno(context))); + pa_threaded_mainloop_unlock(mainloop); + return false; + } + + // Wait until the stream is ready + pa_threaded_mainloop_wait(mainloop); + } + + pa_threaded_mainloop_unlock(mainloop); + + SetVolume(iVolume); + + NOTICE_LOG(AUDIO, "Pulse successfully initialized."); return true; } void PulseAudio::PulseShutdown() { - if (handle != NULL) + if (mainloop) + pa_threaded_mainloop_stop(mainloop); + + if (stream) + pa_stream_unref(stream); + + if (context) { - pa_simple_free(handle); - handle = NULL; + pa_context_disconnect(context); + pa_context_unref(context); + } + + if (mainloop) + pa_threaded_mainloop_free(mainloop); +} + +void PulseAudio::SignalMainLoop() +{ + pa_threaded_mainloop_signal(mainloop, 0); +} + +void PulseAudio::ContextStateCB(pa_context *c, void *userdata) +{ + switch (pa_context_get_state(c)) + { + case PA_CONTEXT_READY: + case PA_CONTEXT_TERMINATED: + case PA_CONTEXT_FAILED: + ((PulseAudio *)userdata)->SignalMainLoop(); + break; + + default: + break; } } +void PulseAudio::StreamStateCB(pa_stream *s, void * userdata) +{ + switch (pa_stream_get_state(s)) + { + case PA_STREAM_READY: + case PA_STREAM_TERMINATED: + case PA_STREAM_FAILED: + ((PulseAudio *)userdata)->SignalMainLoop(); + break; + + default: + break; + } +} + +void PulseAudio::StreamWriteCB(pa_stream *s, size_t length, void *userdata) +{ + ((PulseAudio *)userdata)->SignalMainLoop(); +} + +static bool StateIsGood(pa_context *context, pa_stream *stream) +{ + if (!context || !PA_CONTEXT_IS_GOOD(pa_context_get_state(context)) || + !stream || !PA_STREAM_IS_GOOD(pa_stream_get_state(stream))) + { + if ((context && pa_context_get_state(context) == PA_CONTEXT_FAILED) || + (stream && pa_stream_get_state(stream) == PA_STREAM_FAILED)) + { + ERROR_LOG(AUDIO, "PulseAudio state failure: %s", + pa_strerror(pa_context_errno(context))); + } + else + { + ERROR_LOG(AUDIO, "PulseAudio state failure: %s", + pa_strerror(PA_ERR_BADSTATE)); + } + return false; + } + return true; +} + +bool PulseAudio::Write(const void *data, size_t length) +{ + if (!data || length == 0 || !stream) + return false; + + pa_threaded_mainloop_lock(mainloop); + + if (!StateIsGood(context, stream)) + { + pa_threaded_mainloop_unlock(mainloop); + return false; + } + + while (length > 0) + { + size_t l; + int r; + + while (!(l = pa_stream_writable_size(stream))) + { + pa_threaded_mainloop_wait(mainloop); + if (!StateIsGood(context, stream)) + { + pa_threaded_mainloop_unlock(mainloop); + return false; + } + } + + if (l == (size_t)-1) + { + ERROR_LOG(AUDIO, "PulseAudio invalid stream: %s", + pa_strerror(pa_context_errno(context))); + pa_threaded_mainloop_unlock(mainloop); + return false; + } + + if (l > length) + l = length; + + r = pa_stream_write(stream, data, l, NULL, 0LL, PA_SEEK_RELATIVE); + if (r < 0) + { + ERROR_LOG(AUDIO, "PulseAudio error writing to stream: %s", + pa_strerror(pa_context_errno(context))); + pa_threaded_mainloop_unlock(mainloop); + return false; + } + + data = (const uint8_t*) data + l; + length -= l; + } + + pa_threaded_mainloop_unlock(mainloop); + return true; +} + +void PulseAudio::SetVolume(int volume) +{ + iVolume = volume; + + if (!stream) + return; + + pa_cvolume cvolume; + const pa_channel_map *channels = pa_stream_get_channel_map(stream); + pa_cvolume_set(&cvolume, channels->channels, + iVolume * (PA_VOLUME_NORM - PA_VOLUME_MUTED) / 100); + + pa_context_set_sink_input_volume(context, pa_stream_get_index(stream), + &cvolume, NULL, this); +} diff --git a/Source/Core/AudioCommon/Src/PulseAudioStream.h b/Source/Core/AudioCommon/Src/PulseAudioStream.h index 97fd959282..6a7e82e79f 100644 --- a/Source/Core/AudioCommon/Src/PulseAudioStream.h +++ b/Source/Core/AudioCommon/Src/PulseAudioStream.h @@ -19,9 +19,7 @@ #define _PULSE_AUDIO_STREAM_H #if defined(HAVE_PULSEAUDIO) && HAVE_PULSEAUDIO -#include -#include -#include +#include #endif #include "Common.h" @@ -37,30 +35,34 @@ public: virtual ~PulseAudio(); virtual bool Start(); - virtual void SoundLoop(); virtual void Stop(); + virtual void SetVolume(int volume); - static bool isValid() { - return true; - } - virtual bool usesMixer() const { - return true; - } + static bool isValid() {return true;} + + virtual bool usesMixer() const {return true;} virtual void Update(); private: + virtual void SoundLoop(); + static void *ThreadTrampoline(void *args); bool PulseInit(); void PulseShutdown(); + bool Write(const void *data, size_t bytes); + void SignalMainLoop(); + static void ContextStateCB(pa_context *c, void *userdata); + static void StreamStateCB(pa_stream *s, void * userdata); + static void StreamWriteCB(pa_stream *s, size_t length, void *userdata); u8 *mix_buffer; Common::Thread *thread; - // 0 = continue - // 1 = shutdown - // 2 = done shutting down. - volatile int thread_data; + volatile bool thread_running; - pa_simple *handle; + pa_threaded_mainloop *mainloop; + pa_context *context; + pa_stream *stream; + int iVolume; #else public: PulseAudio(CMixer *mixer) : SoundStream(mixer) {} @@ -68,4 +70,3 @@ public: }; #endif - diff --git a/Source/Plugins/Plugin_DSP_HLE/Src/ConfigDlg.cpp b/Source/Plugins/Plugin_DSP_HLE/Src/ConfigDlg.cpp index 15d6b2e331..068941503e 100644 --- a/Source/Plugins/Plugin_DSP_HLE/Src/ConfigDlg.cpp +++ b/Source/Plugins/Plugin_DSP_HLE/Src/ConfigDlg.cpp @@ -15,7 +15,6 @@ // Official SVN repository and contact information can be found at // http://code.google.com/p/dolphin-emu/ - #include "Config.h" #include "ConfigDlg.h" @@ -28,24 +27,33 @@ BEGIN_EVENT_TABLE(DSPConfigDialogHLE, wxDialog) EVT_COMMAND_SCROLL(ID_VOLUME, DSPConfigDialogHLE::VolumeChanged) END_EVENT_TABLE() -DSPConfigDialogHLE::DSPConfigDialogHLE(wxWindow *parent, wxWindowID id, const wxString &title, const wxPoint &position, const wxSize& size, long style) +DSPConfigDialogHLE::DSPConfigDialogHLE(wxWindow *parent, wxWindowID id, + const wxString &title, const wxPoint &position, const wxSize& size, long style) : wxDialog(parent, id, title, position, size, style) { - m_OK = new wxButton(this, wxID_OK, wxT("OK"), wxDefaultPosition, wxDefaultSize, 0, wxDefaultValidator); - + wxButton *m_OK = new wxButton(this, wxID_OK, wxT("OK"), + wxDefaultPosition, wxDefaultSize, 0, wxDefaultValidator); + wxStaticBoxSizer *sbSettings = new wxStaticBoxSizer(wxVERTICAL, this, wxT("Sound Settings")); wxStaticBoxSizer *sbSettingsV = new wxStaticBoxSizer(wxVERTICAL, this, wxT("Volume")); // Create items - m_buttonEnableHLEAudio = new wxCheckBox(this, ID_ENABLE_HLE_AUDIO, wxT("Enable HLE Audio"), wxDefaultPosition, wxDefaultSize, 0, wxDefaultValidator); - m_buttonEnableDTKMusic = new wxCheckBox(this, ID_ENABLE_DTK_MUSIC, wxT("Enable DTK Music"), wxDefaultPosition, wxDefaultSize, 0, wxDefaultValidator); - m_buttonEnableThrottle = new wxCheckBox(this, ID_ENABLE_THROTTLE, wxT("Enable Audio Throttle"), wxDefaultPosition, wxDefaultSize, 0, wxDefaultValidator); - wxStaticText *BackendText = new wxStaticText(this, wxID_ANY, wxT("Audio Backend"), wxDefaultPosition, wxDefaultSize, 0); - m_BackendSelection = new wxChoice(this, ID_BACKEND, wxDefaultPosition, wxSize(110, 20), wxArrayBackends, 0, wxDefaultValidator, wxEmptyString); + m_buttonEnableHLEAudio = new wxCheckBox(this, ID_ENABLE_HLE_AUDIO, wxT("Enable HLE Audio"), + wxDefaultPosition, wxDefaultSize, 0, wxDefaultValidator); + m_buttonEnableDTKMusic = new wxCheckBox(this, ID_ENABLE_DTK_MUSIC, wxT("Enable DTK Music"), + wxDefaultPosition, wxDefaultSize, 0, wxDefaultValidator); + m_buttonEnableThrottle = new wxCheckBox(this, ID_ENABLE_THROTTLE, wxT("Enable Audio Throttle"), + wxDefaultPosition, wxDefaultSize, 0, wxDefaultValidator); + wxStaticText *BackendText = new wxStaticText(this, wxID_ANY, wxT("Audio Backend"), + wxDefaultPosition, wxDefaultSize, 0); + m_BackendSelection = new wxChoice(this, ID_BACKEND, wxDefaultPosition, wxSize(110, 20), + wxArrayBackends, 0, wxDefaultValidator, wxEmptyString); - m_volumeSlider = new wxSlider(this, ID_VOLUME, ac_Config.m_Volume, 1, 100, wxDefaultPosition, wxDefaultSize, wxSL_VERTICAL|wxSL_INVERSE); + m_volumeSlider = new wxSlider(this, ID_VOLUME, ac_Config.m_Volume, 1, 100, + wxDefaultPosition, wxDefaultSize, wxSL_VERTICAL | wxSL_INVERSE); m_volumeSlider->Enable(SupportsVolumeChanges(ac_Config.sBackend)); - m_volumeText = new wxStaticText(this, wxID_ANY, wxString::Format(wxT("%d %%"), ac_Config.m_Volume), wxDefaultPosition, wxDefaultSize, 0); + m_volumeText = new wxStaticText(this, wxID_ANY, wxString::Format(wxT("%d %%"), + ac_Config.m_Volume), wxDefaultPosition, wxDefaultSize, 0); // Update values m_buttonEnableHLEAudio->SetValue(g_Config.m_EnableHLEAudio ? true : false); @@ -59,8 +67,9 @@ DSPConfigDialogHLE::DSPConfigDialogHLE(wxWindow *parent, wxWindowID id, const wx wxT("Disabling this could cause abnormal game speed, such as too fast.\n") wxT("But sometimes enabling this could cause constant noise.\n") wxT("\nKeyboard Shortcut : Hold down to instantly disable Throttle.")); - m_BackendSelection->SetToolTip(wxT("Changing this will have no effect while the emulator is running!")); - m_volumeSlider->SetToolTip(wxT("This setting only affects DSound and OpenAL.")); + m_BackendSelection-> + SetToolTip(wxT("Changing this will have no effect while the emulator is running!")); + m_volumeSlider->SetToolTip(wxT("This setting only affects DSound, OpenAL, and PulseAudio.")); // Create sizer and add items to dialog wxBoxSizer *sMain = new wxBoxSizer(wxVERTICAL); @@ -75,14 +84,14 @@ DSPConfigDialogHLE::DSPConfigDialogHLE(wxWindow *parent, wxWindowID id, const wx sBackend->Add(m_BackendSelection, 0, wxALL, 1); sbSettings->Add(sBackend, 0, wxALL, 2); - sbSettingsV->Add(m_volumeSlider, 0, wxLEFT|wxRIGHT|wxALIGN_CENTER, 6); + sbSettingsV->Add(m_volumeSlider, 1, wxLEFT|wxRIGHT|wxALIGN_CENTER, 6); sbSettingsV->Add(m_volumeText, 0, wxALL|wxALIGN_LEFT, 4); sSettings->Add(sbSettings, 0, wxALL|wxEXPAND, 4); sSettings->Add(sbSettingsV, 0, wxALL|wxEXPAND, 4); sMain->Add(sSettings, 0, wxALL|wxEXPAND, 4); - - sButtons->AddStretchSpacer(); + + sButtons->AddStretchSpacer(); sButtons->Add(m_OK, 0, wxALL, 1); sMain->Add(sButtons, 0, wxALL|wxEXPAND, 4); SetSizerAndFit(sMain); @@ -145,10 +154,12 @@ bool DSPConfigDialogHLE::SupportsVolumeChanges(std::string backend) // but getting the backend from string etc. is probably // too much just to enable/disable a stupid slider... return (backend == BACKEND_DIRECTSOUND || - backend == BACKEND_OPENAL); + backend == BACKEND_OPENAL || + backend == BACKEND_PULSEAUDIO); } void DSPConfigDialogHLE::BackendChanged(wxCommandEvent& event) { - m_volumeSlider->Enable(SupportsVolumeChanges(std::string(m_BackendSelection->GetStringSelection().mb_str()))); + m_volumeSlider->Enable(SupportsVolumeChanges( + std::string(m_BackendSelection->GetStringSelection().mb_str()))); } diff --git a/Source/Plugins/Plugin_DSP_HLE/Src/ConfigDlg.h b/Source/Plugins/Plugin_DSP_HLE/Src/ConfigDlg.h index 834b5ec9ab..7c606333ab 100644 --- a/Source/Plugins/Plugin_DSP_HLE/Src/ConfigDlg.h +++ b/Source/Plugins/Plugin_DSP_HLE/Src/ConfigDlg.h @@ -36,13 +36,12 @@ public: virtual ~DSPConfigDialogHLE(); void AddBackend(const char *backend); void ClearBackends(); - + private: DECLARE_EVENT_TABLE(); - + wxSlider* m_volumeSlider; wxStaticText* m_volumeText; - wxButton* m_OK; wxCheckBox* m_buttonEnableHLEAudio; wxCheckBox* m_buttonEnableDTKMusic; wxCheckBox* m_buttonEnableThrottle; @@ -57,7 +56,7 @@ private: ID_BACKEND, ID_VOLUME }; - + void OnOK(wxCommandEvent& event); void SettingsChanged(wxCommandEvent& event); void VolumeChanged(wxScrollEvent& event); diff --git a/Source/Plugins/Plugin_DSP_LLE/Src/DSPConfigDlgLLE.cpp b/Source/Plugins/Plugin_DSP_LLE/Src/DSPConfigDlgLLE.cpp index 94b918e340..405294fb07 100644 --- a/Source/Plugins/Plugin_DSP_LLE/Src/DSPConfigDlgLLE.cpp +++ b/Source/Plugins/Plugin_DSP_LLE/Src/DSPConfigDlgLLE.cpp @@ -64,7 +64,7 @@ DSPConfigDialogLLE::DSPConfigDialogLLE(wxWindow *parent, wxWindowID id, const wx m_buttonEnableJIT->SetToolTip(wxT("Enables dynamic recompilation of DSP code.\n") wxT("Changing this will have no effect while the emulator is running!")); m_BackendSelection->SetToolTip(wxT("Changing this will have no effect while the emulator is running!")); - m_volumeSlider->SetToolTip(wxT("This setting only affects DSound and OpenAL.")); + m_volumeSlider->SetToolTip(wxT("This setting only affects DSound, OpenAL, and PulseAudio.")); // Create sizer and add items to dialog wxBoxSizer *sMain = new wxBoxSizer(wxVERTICAL); @@ -80,7 +80,7 @@ DSPConfigDialogLLE::DSPConfigDialogLLE(wxWindow *parent, wxWindowID id, const wx sBackend->Add(m_BackendSelection, 0, wxALL, 1); sbSettings->Add(sBackend, 0, wxALL, 2); - sbSettingsV->Add(m_volumeSlider, 0, wxLEFT|wxRIGHT|wxALIGN_CENTER, 6); + sbSettingsV->Add(m_volumeSlider, 1, wxLEFT|wxRIGHT|wxALIGN_CENTER, 6); sbSettingsV->Add(m_volumeText, 0, wxALL|wxALIGN_LEFT, 4); sSettings->Add(sbSettings, 0, wxALL|wxEXPAND, 4); @@ -151,7 +151,8 @@ bool DSPConfigDialogLLE::SupportsVolumeChanges(std::string backend) // but getting the backend from string etc. is probably // too much just to enable/disable a stupid slider... return (backend == BACKEND_DIRECTSOUND || - backend == BACKEND_OPENAL); + backend == BACKEND_OPENAL || + backend == BACKEND_PULSEAUDIO); } void DSPConfigDialogLLE::BackendChanged(wxCommandEvent& event)