| 
									
										
										
										
											2009-10-15 17:28:23 +00:00
										 |  |  | // Copyright (C) 2003 Dolphin Project.
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // This program is free software: you can redistribute it and/or modify
 | 
					
						
							|  |  |  | // it under the terms of the GNU General Public License as published by
 | 
					
						
							|  |  |  | // the Free Software Foundation, version 2.0.
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // This program is distributed in the hope that it will be useful,
 | 
					
						
							|  |  |  | // but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
					
						
							|  |  |  | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
					
						
							|  |  |  | // GNU General Public License 2.0 for more details.
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // A copy of the GPL 2.0 should have been included with the program.
 | 
					
						
							|  |  |  | // If not, see http://www.gnu.org/licenses/
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Official SVN repository and contact information can be found at
 | 
					
						
							|  |  |  | // http://code.google.com/p/dolphin-emu/
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2011-01-31 08:19:27 +00:00
										 |  |  | #include <functional>
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2009-10-15 17:28:23 +00:00
										 |  |  | #include "Common.h"
 | 
					
						
							|  |  |  | #include "Thread.h"
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #include "PulseAudioStream.h"
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #define BUFFER_SIZE 4096
 | 
					
						
							| 
									
										
										
										
											2010-08-17 02:14:04 +00:00
										 |  |  | #define BUFFER_SIZE_BYTES (BUFFER_SIZE * 4)
 | 
					
						
							| 
									
										
										
										
											2009-10-15 17:28:23 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2010-08-17 02:14:04 +00:00
										 |  |  | PulseAudio::PulseAudio(CMixer *mixer) | 
					
						
							|  |  |  | 	: SoundStream(mixer), thread_running(false), mainloop(NULL) | 
					
						
							|  |  |  | 	, context(NULL), stream(NULL), iVolume(100) | 
					
						
							| 
									
										
										
										
											2009-10-15 17:28:23 +00:00
										 |  |  | { | 
					
						
							|  |  |  | 	mix_buffer = new u8[BUFFER_SIZE_BYTES]; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | PulseAudio::~PulseAudio() | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	delete [] mix_buffer; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | bool PulseAudio::Start() | 
					
						
							|  |  |  | { | 
					
						
							| 
									
										
										
										
											2010-08-17 02:14:04 +00:00
										 |  |  | 	thread_running = true; | 
					
						
							| 
									
										
										
										
											2011-01-31 08:19:27 +00:00
										 |  |  | 	thread = std::thread(std::mem_fun(&PulseAudio::SoundLoop), this); | 
					
						
							| 
									
										
										
										
											2009-10-15 17:28:23 +00:00
										 |  |  | 	return true; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void PulseAudio::Stop() | 
					
						
							|  |  |  | { | 
					
						
							| 
									
										
										
										
											2010-08-17 02:14:04 +00:00
										 |  |  | 	thread_running = false; | 
					
						
							| 
									
										
										
										
											2011-01-27 21:34:37 +00:00
										 |  |  | 	thread.join(); | 
					
						
							| 
									
										
										
										
											2009-10-15 17:28:23 +00:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void PulseAudio::Update() | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	// don't need to do anything here.
 | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Called on audio thread.
 | 
					
						
							|  |  |  | void PulseAudio::SoundLoop() | 
					
						
							|  |  |  | { | 
					
						
							| 
									
										
										
										
											2010-08-17 02:14:04 +00:00
										 |  |  | 	thread_running = PulseInit(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	while (thread_running) | 
					
						
							| 
									
										
										
										
											2009-10-15 17:28:23 +00:00
										 |  |  | 	{ | 
					
						
							|  |  |  | 		int frames_to_deliver = 512; | 
					
						
							| 
									
										
										
										
											2010-08-17 02:14:04 +00:00
										 |  |  | 		m_mixer->Mix((short *)mix_buffer, frames_to_deliver); | 
					
						
							|  |  |  | 		if (!Write(mix_buffer, frames_to_deliver * 4)) | 
					
						
							|  |  |  | 			ERROR_LOG(AUDIO, "PulseAudio failure writing data"); | 
					
						
							| 
									
										
										
										
											2009-10-15 17:28:23 +00:00
										 |  |  | 	} | 
					
						
							|  |  |  | 	PulseShutdown(); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | bool PulseAudio::PulseInit() | 
					
						
							|  |  |  | { | 
					
						
							| 
									
										
										
										
											2010-06-06 04:02:16 +00:00
										 |  |  | 	// The Sample format to use
 | 
					
						
							|  |  |  | 	static const pa_sample_spec ss = { | 
					
						
							|  |  |  | 		PA_SAMPLE_S16LE, | 
					
						
							|  |  |  | 		m_mixer->GetSampleRate(), | 
					
						
							|  |  |  | 		2 | 
					
						
							|  |  |  | 	}; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2010-08-17 02:14:04 +00:00
										 |  |  | 	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); | 
					
						
							| 
									
										
										
										
											2010-06-06 04:02:16 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2010-08-17 02:14:04 +00:00
										 |  |  | 	if (pa_stream_connect_playback(stream, NULL, NULL, PA_STREAM_NOFLAGS, NULL, NULL) < 0) | 
					
						
							| 
									
										
										
										
											2010-06-06 04:02:16 +00:00
										 |  |  | 	{ | 
					
						
							| 
									
										
										
										
											2010-08-17 02:14:04 +00:00
										 |  |  | 		ERROR_LOG(AUDIO, "PulseAudio failed to connect playback stream: %s", | 
					
						
							|  |  |  | 				pa_strerror(pa_context_errno(context))); | 
					
						
							|  |  |  | 		pa_threaded_mainloop_unlock(mainloop); | 
					
						
							| 
									
										
										
										
											2010-06-06 04:02:16 +00:00
										 |  |  | 		return false; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2010-08-17 02:14:04 +00:00
										 |  |  | 	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."); | 
					
						
							| 
									
										
										
										
											2009-10-15 17:28:23 +00:00
										 |  |  | 	return true; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void PulseAudio::PulseShutdown() | 
					
						
							|  |  |  | { | 
					
						
							| 
									
										
										
										
											2010-08-17 02:14:04 +00:00
										 |  |  | 	if (mainloop) | 
					
						
							|  |  |  | 		pa_threaded_mainloop_stop(mainloop); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (stream) | 
					
						
							|  |  |  | 		pa_stream_unref(stream); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (context) | 
					
						
							| 
									
										
										
										
											2009-10-15 17:28:23 +00:00
										 |  |  | 	{ | 
					
						
							| 
									
										
										
										
											2010-08-17 02:14:04 +00:00
										 |  |  | 		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; | 
					
						
							| 
									
										
										
										
											2009-10-15 17:28:23 +00:00
										 |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2010-08-17 02:14:04 +00:00
										 |  |  | 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); | 
					
						
							|  |  |  | } |