diff options
| author | Glenn Rice <glennricster@gmail.com> | 2010-08-17 02:14:04 +0000 |
|---|---|---|
| committer | Glenn Rice <glennricster@gmail.com> | 2010-08-17 02:14:04 +0000 |
| commit | 7866fade02b507f76971beb74f63707ac5ba18c6 (patch) | |
| tree | 5d08261e99f14be31aa9d524414eaa2333ef8273 /Source/Core/AudioCommon/Src/PulseAudioStream.cpp | |
| parent | fb1c14e2ccd147a17ebaac2d162ce063cd0ca756 (diff) | |
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
Diffstat (limited to 'Source/Core/AudioCommon/Src/PulseAudioStream.cpp')
| -rw-r--r-- | Source/Core/AudioCommon/Src/PulseAudioStream.cpp | 261 |
1 files changed, 235 insertions, 26 deletions
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<PulseAudio *>(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<short *>(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(); + + 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 failed to connect context: %s", + pa_strerror(pa_context_errno(context))); + return false; + } + + 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 (!(handle = pa_simple_new(NULL, "dolphin-emu", PA_STREAM_PLAYBACK, NULL, - "emulator", &ss, NULL, NULL, &err))) + if (pa_stream_connect_playback(stream, NULL, NULL, PA_STREAM_NOFLAGS, NULL, NULL) < 0) { - ERROR_LOG(AUDIO, "PulseAudio open error: %s\n", pa_strerror(err)); + ERROR_LOG(AUDIO, "PulseAudio failed to connect playback stream: %s", + pa_strerror(pa_context_errno(context))); + pa_threaded_mainloop_unlock(mainloop); return false; } - NOTICE_LOG(AUDIO, "Pulse successfully initialized.\n"); + 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); +} |
