From 9574d11dc80248753c7fee22f3621879067c285b Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Fri, 21 Feb 2014 23:17:52 +0100 Subject: [PATCH] output/sles: new output plugin for Android --- Makefile.am | 11 + 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 + 8 files changed, 828 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 diff --git a/Makefile.am b/Makefile.am index 35f43bae0..7e05cd375 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1078,6 +1078,17 @@ liboutput_plugins_a_SOURCES += \ libmixer_plugins_a_SOURCES += src/mixer/plugins/AlsaMixerPlugin.cxx endif +if ANDROID +liboutput_plugins_a_SOURCES += \ + src/output/plugins/sles/Object.hxx \ + src/output/plugins/sles/Engine.hxx \ + src/output/plugins/sles/Play.hxx \ + src/output/plugins/sles/AndroidSimpleBufferQueue.hxx \ + src/output/plugins/sles/SlesOutputPlugin.cxx \ + src/output/plugins/sles/SlesOutputPlugin.hxx +OUTPUT_LIBS += -lOpenSLES +endif + if HAVE_ROAR liboutput_plugins_a_SOURCES += \ src/output/plugins/RoarOutputPlugin.cxx \ 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 + * + * 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 + +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 + * + * 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 + +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 + * + * 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 + +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 + * + * 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 + +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 +#include + +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 ¶m, Error &error) { + return base.Configure(param, error); + } + + bool Configure(const config_param ¶m, 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 ¶m, 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