From 9574d11dc80248753c7fee22f3621879067c285b Mon Sep 17 00:00:00 2001
From: Max Kellermann <max@duempel.org>
Date: Fri, 21 Feb 2014 23:17:52 +0100
Subject: output/sles: new output plugin for Android

---
 src/output/Registry.cxx                            |   4 +
 .../plugins/sles/AndroidSimpleBufferQueue.hxx      |  67 +++
 src/output/plugins/sles/Engine.hxx                 |  68 +++
 src/output/plugins/sles/Object.hxx                 |  64 +++
 src/output/plugins/sles/Play.hxx                   |  52 ++
 src/output/plugins/sles/SlesOutputPlugin.cxx       | 537 +++++++++++++++++++++
 src/output/plugins/sles/SlesOutputPlugin.hxx       |  25 +
 7 files changed, 817 insertions(+)
 create mode 100644 src/output/plugins/sles/AndroidSimpleBufferQueue.hxx
 create mode 100644 src/output/plugins/sles/Engine.hxx
 create mode 100644 src/output/plugins/sles/Object.hxx
 create mode 100644 src/output/plugins/sles/Play.hxx
 create mode 100644 src/output/plugins/sles/SlesOutputPlugin.cxx
 create mode 100644 src/output/plugins/sles/SlesOutputPlugin.hxx

(limited to 'src/output')

diff --git a/src/output/Registry.cxx b/src/output/Registry.cxx
index 42da59d15..566f6b6a8 100644
--- a/src/output/Registry.cxx
+++ b/src/output/Registry.cxx
@@ -34,6 +34,7 @@
 #include "plugins/RecorderOutputPlugin.hxx"
 #include "plugins/RoarOutputPlugin.hxx"
 #include "plugins/ShoutOutputPlugin.hxx"
+#include "plugins/sles/SlesOutputPlugin.hxx"
 #include "plugins/SolarisOutputPlugin.hxx"
 #include "plugins/WinmmOutputPlugin.hxx"
 
@@ -44,6 +45,9 @@ const AudioOutputPlugin *const audio_output_plugins[] = {
 	&shout_output_plugin,
 #endif
 	&null_output_plugin,
+#ifdef ANDROID
+	&sles_output_plugin,
+#endif
 #ifdef HAVE_FIFO
 	&fifo_output_plugin,
 #endif
diff --git a/src/output/plugins/sles/AndroidSimpleBufferQueue.hxx b/src/output/plugins/sles/AndroidSimpleBufferQueue.hxx
new file mode 100644
index 000000000..c7dd4ccca
--- /dev/null
+++ b/src/output/plugins/sles/AndroidSimpleBufferQueue.hxx
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2011-2012 Max Kellermann <max@duempel.org>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the
+ * distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+ * FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+ * OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef SLES_ANDROID_SIMPLE_BUFFER_QUEUE_HPP
+#define SLES_ANDROID_SIMPLE_BUFFER_QUEUE_HPP
+
+#include <SLES/OpenSLES_Android.h>
+
+namespace SLES {
+	/**
+	 * OO wrapper for an OpenSL/ES SLAndroidSimpleBufferQueueItf
+	 * variable.
+	 */
+	class AndroidSimpleBufferQueue {
+		SLAndroidSimpleBufferQueueItf queue;
+
+	public:
+		AndroidSimpleBufferQueue() = default;
+		explicit AndroidSimpleBufferQueue(SLAndroidSimpleBufferQueueItf _queue)
+			:queue(_queue) {}
+
+		SLresult Enqueue(const void *pBuffer, SLuint32 size) {
+			return (*queue)->Enqueue(queue, pBuffer, size);
+		}
+
+		SLresult Clear() {
+			return (*queue)->Clear(queue);
+		}
+
+		SLresult GetState(SLAndroidSimpleBufferQueueState *pState) {
+			return (*queue)->GetState(queue, pState);
+		}
+
+		SLresult RegisterCallback(slAndroidSimpleBufferQueueCallback callback,
+					  void *pContext) {
+			return (*queue)->RegisterCallback(queue, callback, pContext);
+		}
+	};
+}
+
+#endif
diff --git a/src/output/plugins/sles/Engine.hxx b/src/output/plugins/sles/Engine.hxx
new file mode 100644
index 000000000..7c6e3cf50
--- /dev/null
+++ b/src/output/plugins/sles/Engine.hxx
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2011-2012 Max Kellermann <max@duempel.org>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the
+ * distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+ * FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+ * OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef SLES_ENGINE_HPP
+#define SLES_ENGINE_HPP
+
+#include <SLES/OpenSLES.h>
+
+namespace SLES {
+	/**
+	 * OO wrapper for an OpenSL/ES SLEngineItf variable.
+	 */
+	class Engine {
+		SLEngineItf engine;
+
+	public:
+		Engine() = default;
+		explicit Engine(SLEngineItf _engine):engine(_engine) {}
+
+		SLresult CreateAudioPlayer(SLObjectItf *pPlayer,
+					   SLDataSource *pAudioSrc, SLDataSink *pAudioSnk,
+					   SLuint32 numInterfaces,
+					   const SLInterfaceID *pInterfaceIds,
+					   const SLboolean *pInterfaceRequired) {
+			return (*engine)->CreateAudioPlayer(engine, pPlayer,
+							    pAudioSrc, pAudioSnk,
+							    numInterfaces, pInterfaceIds,
+							    pInterfaceRequired);
+		}
+
+		SLresult CreateOutputMix(SLObjectItf *pMix,
+					 SLuint32 numInterfaces,
+					 const SLInterfaceID *pInterfaceIds,
+					 const SLboolean *pInterfaceRequired) {
+			return (*engine)->CreateOutputMix(engine, pMix,
+							  numInterfaces, pInterfaceIds,
+							  pInterfaceRequired);
+		}
+	};
+}
+
+#endif
diff --git a/src/output/plugins/sles/Object.hxx b/src/output/plugins/sles/Object.hxx
new file mode 100644
index 000000000..852d62d0d
--- /dev/null
+++ b/src/output/plugins/sles/Object.hxx
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2011-2012 Max Kellermann <max@duempel.org>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the
+ * distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+ * FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+ * OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef SLES_OBJECT_HPP
+#define SLES_OBJECT_HPP
+
+#include <SLES/OpenSLES.h>
+
+namespace SLES {
+	/**
+	 * OO wrapper for an OpenSL/ES SLObjectItf variable.
+	 */
+	class Object {
+		SLObjectItf object;
+
+	public:
+		Object() = default;
+		explicit Object(SLObjectItf _object):object(_object) {}
+
+		operator SLObjectItf() {
+			return object;
+		}
+
+		SLresult Realize(bool async) {
+			return (*object)->Realize(object, async);
+		}
+
+		void Destroy() {
+			(*object)->Destroy(object);
+		}
+
+		SLresult GetInterface(const SLInterfaceID iid, void *pInterface) {
+			return (*object)->GetInterface(object, iid, pInterface);
+		}
+	};
+}
+
+#endif
diff --git a/src/output/plugins/sles/Play.hxx b/src/output/plugins/sles/Play.hxx
new file mode 100644
index 000000000..c760151ef
--- /dev/null
+++ b/src/output/plugins/sles/Play.hxx
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2011-2012 Max Kellermann <max@duempel.org>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the
+ * distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+ * FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+ * OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef SLES_PLAY_HPP
+#define SLES_PLAY_HPP
+
+#include <SLES/OpenSLES.h>
+
+namespace SLES {
+	/**
+	 * OO wrapper for an OpenSL/ES SLPlayItf variable.
+	 */
+	class Play {
+		SLPlayItf play;
+
+	public:
+		Play() = default;
+		explicit Play(SLPlayItf _play):play(_play) {}
+
+		SLresult SetPlayState(SLuint32 state) {
+			return (*play)->SetPlayState(play, state);
+		}
+	};
+}
+
+#endif
diff --git a/src/output/plugins/sles/SlesOutputPlugin.cxx b/src/output/plugins/sles/SlesOutputPlugin.cxx
new file mode 100644
index 000000000..f846b5cfa
--- /dev/null
+++ b/src/output/plugins/sles/SlesOutputPlugin.cxx
@@ -0,0 +1,537 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * 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; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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 for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "SlesOutputPlugin.hxx"
+#include "Object.hxx"
+#include "Engine.hxx"
+#include "Play.hxx"
+#include "AndroidSimpleBufferQueue.hxx"
+#include "../../OutputAPI.hxx"
+#include "util/Macros.hxx"
+#include "util/Error.hxx"
+#include "util/Domain.hxx"
+#include "system/ByteOrder.hxx"
+#include "Log.hxx"
+
+#include <SLES/OpenSLES.h>
+#include <SLES/OpenSLES_Android.h>
+
+class SlesOutput {
+	static constexpr unsigned N_BUFFERS = 3;
+	static constexpr size_t BUFFER_SIZE = 65536;
+
+	AudioOutput base;
+
+	SLES::Object engine_object, mix_object, play_object;
+	SLES::Play play;
+	SLES::AndroidSimpleBufferQueue queue;
+
+	/**
+	 * This mutex protects the attributes "next" and "filled".  It
+	 * is only needed while playback is launched, when the initial
+	 * buffers are being enqueued in the caller thread, while
+	 * another thread may invoke the registered callback.
+	 */
+	Mutex mutex;
+
+	Cond cond;
+
+	bool pause, cancel;
+
+	/**
+	 * The number of buffers queued to OpenSLES.
+	 */
+	unsigned n_queued;
+
+	/**
+	 * The index of the next buffer to be enqueued.
+	 */
+	unsigned next;
+
+	/**
+	 * Does the "next" buffer already contain synthesised samples?
+	 * This can happen when PCMSynthesiser::Synthesise() has been
+	 * called, but the OpenSL/ES buffer queue was full.  The
+	 * buffer will then be postponed.
+	 */
+	unsigned filled;
+
+	/**
+	 * An array of buffers.  It's one more than being managed by
+	 * OpenSL/ES, and the one not enqueued (see attribute #next)
+	 * will be written to.
+	 */
+	uint8_t buffers[N_BUFFERS][BUFFER_SIZE];
+
+public:
+	SlesOutput()
+		:base(sles_output_plugin) {}
+
+	operator AudioOutput *() {
+		return &base;
+	}
+
+	bool Initialize(const config_param &param, Error &error) {
+		return base.Configure(param, error);
+	}
+
+	bool Configure(const config_param &param, Error &error);
+
+	bool Open(AudioFormat &audio_format, Error &error);
+	void Close();
+
+	unsigned Delay() {
+		return pause && !cancel ? 100 : 0;
+	}
+
+	size_t Play(const void *chunk, size_t size, Error &error);
+
+	void Drain();
+	void Cancel();
+	bool Pause();
+
+private:
+	void PlayedCallback();
+
+	/**
+	 * OpenSL/ES callback which gets invoked when a buffer has
+	 * been consumed.  It synthesises and enqueues the next
+	 * buffer.
+	 */
+	static void PlayedCallback(gcc_unused SLAndroidSimpleBufferQueueItf caller,
+				   void *pContext)
+	{
+		SlesOutput &sles = *(SlesOutput *)pContext;
+		sles.PlayedCallback();
+	}
+};
+
+static constexpr Domain sles_domain("sles");
+
+inline bool
+SlesOutput::Configure(const config_param &, Error &)
+{
+	return true;
+}
+
+inline bool
+SlesOutput::Open(AudioFormat &audio_format, Error &error)
+{
+	SLresult result;
+	SLObjectItf _object;
+
+	result = slCreateEngine(&_object, 0, nullptr, 0,
+				nullptr, nullptr);
+	if (result != SL_RESULT_SUCCESS) {
+		error.Set(sles_domain, int(result), "slCreateEngine() failed");
+		return false;
+	}
+
+	engine_object = SLES::Object(_object);
+
+	result = engine_object.Realize(false);
+	if (result != SL_RESULT_SUCCESS) {
+		error.Set(sles_domain, int(result), "Engine.Realize() failed");
+		engine_object.Destroy();
+		return false;
+	}
+
+	SLEngineItf _engine;
+	result = engine_object.GetInterface(SL_IID_ENGINE, &_engine);
+	if (result != SL_RESULT_SUCCESS) {
+		error.Set(sles_domain, int(result),
+			  "Engine.GetInterface(IID_ENGINE) failed");
+		engine_object.Destroy();
+		return false;
+	}
+
+	SLES::Engine engine(_engine);
+
+	result = engine.CreateOutputMix(&_object, 0, nullptr, nullptr);
+	if (result != SL_RESULT_SUCCESS) {
+		error.Set(sles_domain, int(result),
+			  "Engine.CreateOutputMix() failed");
+		engine_object.Destroy();
+		return false;
+	}
+
+	mix_object = SLES::Object(_object);
+
+	result = mix_object.Realize(false);
+	if (result != SL_RESULT_SUCCESS) {
+		error.Set(sles_domain, int(result),
+			  "Mix.Realize() failed");
+		mix_object.Destroy();
+		engine_object.Destroy();
+		return false;
+	}
+
+	SLDataLocator_AndroidSimpleBufferQueue loc_bufq = {
+		SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE,
+		N_BUFFERS,
+	};
+
+	SLDataFormat_PCM format_pcm;
+	format_pcm.formatType = SL_DATAFORMAT_PCM;
+	format_pcm.numChannels = 1;
+	/* from the Android NDK docs: "Note that the field samplesPerSec is
+	   actually in units of milliHz, despite the misleading name." */
+	format_pcm.samplesPerSec = audio_format.sample_rate * 1000u;
+	format_pcm.bitsPerSample = SL_PCMSAMPLEFORMAT_FIXED_16;
+	format_pcm.containerSize = SL_PCMSAMPLEFORMAT_FIXED_16;
+	format_pcm.channelMask = SL_SPEAKER_FRONT_CENTER;
+	format_pcm.endianness = IsLittleEndian()
+		? SL_BYTEORDER_LITTLEENDIAN
+		: SL_BYTEORDER_BIGENDIAN;
+
+	SLDataSource audioSrc = { &loc_bufq, &format_pcm };
+
+	SLDataLocator_OutputMix loc_outmix = {
+		SL_DATALOCATOR_OUTPUTMIX,
+		mix_object,
+	};
+
+	SLDataSink audioSnk = {
+		&loc_outmix,
+		nullptr,
+	};
+
+	const SLInterfaceID ids2[] = {
+		SL_IID_PLAY,
+		SL_IID_ANDROIDSIMPLEBUFFERQUEUE,
+		SL_IID_ANDROIDCONFIGURATION,
+	};
+
+	static constexpr SLboolean req2[] = {
+		SL_BOOLEAN_TRUE,
+		SL_BOOLEAN_TRUE,
+		SL_BOOLEAN_TRUE,
+	};
+
+	result = engine.CreateAudioPlayer(&_object, &audioSrc, &audioSnk,
+					  ARRAY_SIZE(ids2), ids2, req2);
+	if (result != SL_RESULT_SUCCESS) {
+		error.Set(sles_domain, int(result),
+			  "Engine.CreateAudioPlayer() failed");
+		mix_object.Destroy();
+		engine_object.Destroy();
+		return false;
+	}
+
+	play_object = SLES::Object(_object);
+
+	SLAndroidConfigurationItf android_config;
+	if (play_object.GetInterface(SL_IID_ANDROIDCONFIGURATION,
+				     &android_config) == SL_RESULT_SUCCESS) {
+		SLint32 stream_type = SL_ANDROID_STREAM_MEDIA;
+		(*android_config)->SetConfiguration(android_config,
+						    SL_ANDROID_KEY_STREAM_TYPE,
+						    &stream_type,
+						    sizeof(stream_type));
+	}
+
+	result = play_object.Realize(false);
+
+	if (result != SL_RESULT_SUCCESS) {
+		error.Set(sles_domain, int(result),
+			  "Play.Realize() failed");
+		play_object.Destroy();
+		mix_object.Destroy();
+		engine_object.Destroy();
+		return false;
+	}
+
+	SLPlayItf _play;
+	result = play_object.GetInterface(SL_IID_PLAY, &_play);
+	if (result != SL_RESULT_SUCCESS) {
+		error.Set(sles_domain, int(result),
+			  "Play.GetInterface(IID_PLAY) failed");
+		play_object.Destroy();
+		mix_object.Destroy();
+		engine_object.Destroy();
+		return false;
+	}
+
+	play = SLES::Play(_play);
+
+	SLAndroidSimpleBufferQueueItf _queue;
+	result = play_object.GetInterface(SL_IID_ANDROIDSIMPLEBUFFERQUEUE,
+					  &_queue);
+	if (result != SL_RESULT_SUCCESS) {
+		error.Set(sles_domain, int(result),
+			  "Play.GetInterface(IID_ANDROIDSIMPLEBUFFERQUEUE) failed");
+		play_object.Destroy();
+		mix_object.Destroy();
+		engine_object.Destroy();
+		return false;
+	}
+
+	queue = SLES::AndroidSimpleBufferQueue(_queue);
+	result = queue.RegisterCallback(PlayedCallback, (void *)this);
+	if (result != SL_RESULT_SUCCESS) {
+		error.Set(sles_domain, int(result),
+			  "Play.RegisterCallback() failed");
+		play_object.Destroy();
+		mix_object.Destroy();
+		engine_object.Destroy();
+		return false;
+	}
+
+	result = play.SetPlayState(SL_PLAYSTATE_PLAYING);
+	if (result != SL_RESULT_SUCCESS) {
+		error.Set(sles_domain, int(result),
+			  "Play.SetPlayState(PLAYING) failed");
+		play_object.Destroy();
+		mix_object.Destroy();
+		engine_object.Destroy();
+		return false;
+	}
+
+	pause = cancel = false;
+	n_queued = 0;
+	next = 0;
+	filled = 0;
+
+	// TODO: support other sample formats
+	audio_format.format = SampleFormat::S16;
+
+	// TODO: support stereo
+	audio_format.channels = 1;
+
+	return true;
+}
+
+inline void
+SlesOutput::Close()
+{
+	play.SetPlayState(SL_PLAYSTATE_STOPPED);
+	play_object.Destroy();
+	mix_object.Destroy();
+	engine_object.Destroy();
+}
+
+inline size_t
+SlesOutput::Play(const void *chunk, size_t size, Error &error)
+{
+	cancel = false;
+
+	if (pause) {
+		SLresult result = play.SetPlayState(SL_PLAYSTATE_PLAYING);
+		if (result != SL_RESULT_SUCCESS) {
+			error.Set(sles_domain, int(result),
+				  "Play.SetPlayState(PLAYING) failed");
+			return false;
+		}
+
+		pause = false;
+	}
+
+	const ScopeLock protect(mutex);
+
+	assert(filled < BUFFER_SIZE);
+
+	while (n_queued == N_BUFFERS) {
+		assert(filled == 0);
+		cond.wait(mutex);
+	}
+
+	size_t nbytes = std::min(BUFFER_SIZE - filled, size);
+	memcpy(buffers[next] + filled, chunk, nbytes);
+	filled += nbytes;
+	if (filled < BUFFER_SIZE)
+		return nbytes;
+
+	SLresult result = queue.Enqueue(buffers[next], BUFFER_SIZE);
+	if (result != SL_RESULT_SUCCESS) {
+		error.Set(sles_domain, int(result),
+			  "AndroidSimpleBufferQueue.Enqueue() failed");
+		return 0;
+	}
+
+	++n_queued;
+	next = (next + 1) % N_BUFFERS;
+	filled = 0;
+
+	return nbytes;
+}
+
+inline void
+SlesOutput::Drain()
+{
+	const ScopeLock protect(mutex);
+
+	assert(filled < BUFFER_SIZE);
+
+	while (n_queued > 0)
+		cond.wait(mutex);
+}
+
+inline void
+SlesOutput::Cancel()
+{
+	pause = true;
+	cancel = true;
+
+	SLresult result = play.SetPlayState(SL_PLAYSTATE_PAUSED);
+	if (result != SL_RESULT_SUCCESS)
+		FormatError(sles_domain,  "Play.SetPlayState(PAUSED) failed");
+
+	result = queue.Clear();
+	if (result != SL_RESULT_SUCCESS)
+		FormatWarning(sles_domain,
+			      "AndroidSimpleBufferQueue.Clear() failed");
+
+	const ScopeLock protect(mutex);
+	n_queued = 0;
+	filled = 0;
+}
+
+inline bool
+SlesOutput::Pause()
+{
+	cancel = false;
+
+	if (pause)
+		return true;
+
+	pause = true;
+
+	SLresult result = play.SetPlayState(SL_PLAYSTATE_PAUSED);
+	if (result != SL_RESULT_SUCCESS) {
+		FormatError(sles_domain, "Play.SetPlayState(PAUSED) failed");
+		return false;
+	}
+
+	return true;
+}
+
+inline void
+SlesOutput::PlayedCallback()
+{
+	const ScopeLock protect(mutex);
+	assert(n_queued > 0);
+	--n_queued;
+	cond.signal();
+}
+
+static bool
+sles_test_default_device()
+{
+	/* this is the default output plugin on Android, and it should
+	   be available in any case */
+	return true;
+}
+
+static AudioOutput *
+sles_output_init(const config_param &param, Error &error)
+{
+	SlesOutput *sles = new SlesOutput();
+
+	if (!sles->Initialize(param, error) ||
+	    !sles->Configure(param, error)) {
+		delete sles;
+		return nullptr;
+	}
+
+	return *sles;
+}
+
+static void
+sles_output_finish(AudioOutput *ao)
+{
+	SlesOutput *sles = (SlesOutput *)ao;
+
+	delete sles;
+}
+
+static bool
+sles_output_open(AudioOutput *ao, AudioFormat &audio_format, Error &error)
+{
+	SlesOutput &sles = *(SlesOutput *)ao;
+
+	return sles.Open(audio_format, error);
+}
+
+static void
+sles_output_close(AudioOutput *ao)
+{
+	SlesOutput &sles = *(SlesOutput *)ao;
+
+	sles.Close();
+}
+
+static unsigned
+sles_output_delay(AudioOutput *ao)
+{
+	SlesOutput &sles = *(SlesOutput *)ao;
+
+	return sles.Delay();
+}
+
+static size_t
+sles_output_play(AudioOutput *ao, const void *chunk, size_t size,
+		 Error &error)
+{
+	SlesOutput &sles = *(SlesOutput *)ao;
+
+	return sles.Play(chunk, size, error);
+}
+
+static void
+sles_output_drain(AudioOutput *ao)
+{
+	SlesOutput &sles = *(SlesOutput *)ao;
+
+	sles.Drain();
+}
+
+static void
+sles_output_cancel(AudioOutput *ao)
+{
+	SlesOutput &sles = *(SlesOutput *)ao;
+
+	sles.Cancel();
+}
+
+static bool
+sles_output_pause(AudioOutput *ao)
+{
+	SlesOutput &sles = *(SlesOutput *)ao;
+
+	return sles.Pause();
+}
+
+const struct AudioOutputPlugin sles_output_plugin = {
+	"sles",
+	sles_test_default_device,
+	sles_output_init,
+	sles_output_finish,
+	nullptr,
+	nullptr,
+	sles_output_open,
+	sles_output_close,
+	sles_output_delay,
+	nullptr,
+	sles_output_play,
+	sles_output_drain,
+	sles_output_cancel,
+	sles_output_pause,
+	nullptr,
+};
diff --git a/src/output/plugins/sles/SlesOutputPlugin.hxx b/src/output/plugins/sles/SlesOutputPlugin.hxx
new file mode 100644
index 000000000..5424dec2e
--- /dev/null
+++ b/src/output/plugins/sles/SlesOutputPlugin.hxx
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * 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; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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 for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_SLES_OUTPUT_PLUGIN_HXX
+#define MPD_SLES_OUTPUT_PLUGIN_HXX
+
+extern const struct AudioOutputPlugin sles_output_plugin;
+
+#endif
-- 
cgit v1.2.3