summaryrefslogtreecommitdiff
path: root/Source/Core/AudioCommon/Src/PulseAudioStream.cpp
diff options
context:
space:
mode:
authorGlenn Rice <glennricster@gmail.com>2010-08-17 02:14:04 +0000
committerGlenn Rice <glennricster@gmail.com>2010-08-17 02:14:04 +0000
commit7866fade02b507f76971beb74f63707ac5ba18c6 (patch)
tree5d08261e99f14be31aa9d524414eaa2333ef8273 /Source/Core/AudioCommon/Src/PulseAudioStream.cpp
parentfb1c14e2ccd147a17ebaac2d162ce063cd0ca756 (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.cpp261
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);
+}