diff --git a/CMakeLists.txt b/CMakeLists.txt index da89a7aa9..0f75b38e9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -87,6 +87,7 @@ add_dependencies(taglibreader taglib) if (CMAKE_SYSTEM_NAME MATCHES "Linux") add_subdirectory(src/contrib/alsaout) + add_subdirectory(src/contrib/pulseout) else (CMAKE_SYSTEM_NAME MATCHES "Linux") # macos add_subdirectory(src/contrib/coreaudioout) @@ -107,6 +108,7 @@ if (CMAKE_SYSTEM_NAME MATCHES "Linux") install( FILES bin/plugins/libalsaout.so + bin/plugins/libpulseout.so bin/plugins/libflacdecoder.so bin/plugins/libm4adecoder.so bin/plugins/libmpg123decoder.so diff --git a/src/contrib/pulseout/CMakeLists.txt b/src/contrib/pulseout/CMakeLists.txt new file mode 100755 index 000000000..fd6a09bcf --- /dev/null +++ b/src/contrib/pulseout/CMakeLists.txt @@ -0,0 +1,12 @@ +set (pulseout_SOURCES + pulseout_plugin.cpp + pulse_blocking_stream.c + PulseOut.cpp +) + +add_definitions( + -D_DEBUG +) + +add_library( pulseout SHARED ${pulseout_SOURCES} ) +target_link_libraries( pulseout ${musikbox_LINK_LIBS} pulse) diff --git a/src/contrib/pulseout/PulseOut.cpp b/src/contrib/pulseout/PulseOut.cpp new file mode 100755 index 000000000..1b621aec9 --- /dev/null +++ b/src/contrib/pulseout/PulseOut.cpp @@ -0,0 +1,183 @@ +////////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2007-2016 musikcube team +// +// All rights reserved. +// +// 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. +// +// * Neither the name of the author nor the names of other contributors may +// be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// 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 COPYRIGHT OWNER 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. +// +////////////////////////////////////////////////////////////////////////////// + +#include "PulseOut.h" +#include +#include + +using namespace musik::core::sdk; + +typedef std::unique_lock Lock; + +PulseOut::PulseOut() { + std::cerr << "PulseOut::PulseOut() called" << std::endl; + this->audioConnection = nullptr; + this->state = StateStopped; + this->volume = 1.0f; + this->channels = 0; + this->rate = 0; +} + +PulseOut::~PulseOut() { + std::cerr << "PulseOut: destructor\n"; + this->CloseDevice(); +} + +void PulseOut::CloseDevice() { + std::cerr << "PulseOut: closing device\n"; + + Lock lock(this->stateMutex); + if (this->audioConnection) { + int error = 0; + pa_blocking_flush(this->audioConnection, &error); + pa_blocking_free(this->audioConnection); + this->audioConnection = nullptr; + this->rate = 0; + this->channels = 0; + } +} + +void PulseOut::OpenDevice(musik::core::sdk::IBuffer* buffer) { + + if (!this->audioConnection || + this->rate != buffer->SampleRate() || + this->channels != buffer->Channels()) + { + this->CloseDevice(); + + pa_sample_spec spec; + spec.format = PA_SAMPLE_FLOAT32LE; + spec.channels = buffer->Channels(); + spec.rate = buffer->SampleRate(); + + std::cerr << "PulseOut: opening device\n"; + this->audioConnection = pa_blocking_new( + nullptr, + "musikbox", + PA_STREAM_PLAYBACK, + nullptr, + "music", + &spec, + nullptr, + nullptr, + 0); + + if (this->audioConnection) { + this->SetVolume(this->volume); + this->rate = buffer->SampleRate(); + this->channels = buffer->Channels(); + this->state = StatePlaying; + } + } +} + +void PulseOut::Destroy() { + std::cerr << "PulseOut: destroy\n"; + delete this; +} + +void PulseOut::Stop() { + std::cerr << "PulseOut: stop\n"; + + Lock lock(this->stateMutex); + if (this->audioConnection) { + pa_blocking_flush(this->audioConnection, 0); + this->state = StateStopped; + } +} + +void PulseOut::Pause() { + std::cerr << "PulseOut: pause\n"; + + Lock lock(this->stateMutex); + if (this->audioConnection) { + pa_blocking_flush(this->audioConnection, 0); + this->state = StatePaused; + } +} + +void PulseOut::Resume() { + std::cerr << "PulseOut: resume\n"; + + Lock lock(this->stateMutex); + if (this->audioConnection) { + this->state = StatePlaying; + } +} + +void PulseOut::SetVolume(double volume) { + std::cerr << "PulseOut: volume\n"; + + Lock lock(this->stateMutex); + if (this->audioConnection) { + this->volume = volume; + int normalized = (int) round((double) PA_VOLUME_NORM * volume); + pa_blocking_set_volume(this->audioConnection, normalized, 0); + } +} + +bool PulseOut::Play(IBuffer *buffer, IBufferProvider* provider) { + int error = 0; + + { + Lock lock(this->stateMutex); + + this->OpenDevice(buffer); + + if (!this->audioConnection || this->state != StatePlaying) { + return false; + } + + pa_blocking_write( + this->audioConnection, + buffer->BufferPointer(), + buffer->Samples() * sizeof(float), + &error); + } + + provider->OnBufferProcessed(buffer); + return true; +} + +double PulseOut::Latency() { + Lock lock(this->stateMutex); + + if (!this->audioConnection) { + return 0.0; + } + + int error = 0; + unsigned long long latency = pa_blocking_get_latency(this->audioConnection, &error); + return (double) latency / 1000000.0; /* microseconds to seconds */ +} diff --git a/src/contrib/pulseout/PulseOut.h b/src/contrib/pulseout/PulseOut.h new file mode 100755 index 000000000..d3d82e906 --- /dev/null +++ b/src/contrib/pulseout/PulseOut.h @@ -0,0 +1,73 @@ +////////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2007-2016 musikcube team +// +// All rights reserved. +// +// 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. +// +// * Neither the name of the author nor the names of other contributors may +// be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// 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 COPYRIGHT OWNER 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. +// +////////////////////////////////////////////////////////////////////////////// +#pragma once + +#include "pch.h" + +#include +#include +#include "pulse_blocking_stream.h" + +class PulseOut : public musik::core::sdk::IOutput { + public: + PulseOut(); + virtual ~PulseOut(); + + virtual void Destroy(); + virtual void Pause(); + virtual void Resume(); + virtual void SetVolume(double volume); + virtual void Stop(); + virtual double Latency(); + + virtual bool Play( + musik::core::sdk::IBuffer *buffer, + musik::core::sdk::IBufferProvider *provider); + + private: + enum State { + StateStopped, + StatePaused, + StatePlaying + }; + + void OpenDevice(musik::core::sdk::IBuffer *buffer); + void CloseDevice(); + + std::recursive_mutex stateMutex; + pa_blocking* audioConnection; + State state; + int channels, rate; + double volume; +}; diff --git a/src/contrib/pulseout/pch.h b/src/contrib/pulseout/pch.h new file mode 100755 index 000000000..658bc6360 --- /dev/null +++ b/src/contrib/pulseout/pch.h @@ -0,0 +1,41 @@ +////////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2007-2016 musikcube team +// +// All rights reserved. +// +// 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. +// +// * Neither the name of the author nor the names of other contributors may +// be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// 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 COPYRIGHT OWNER 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. +// +////////////////////////////////////////////////////////////////////////////// + +// Precompiled headers +#pragma once + +#include +#include + +#include diff --git a/src/contrib/pulseout/pulse_blocking_stream.c b/src/contrib/pulseout/pulse_blocking_stream.c new file mode 100644 index 000000000..151b2cc0a --- /dev/null +++ b/src/contrib/pulseout/pulse_blocking_stream.c @@ -0,0 +1,553 @@ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation; either version 2.1 of the License, + or (at your option) any later version. + + PulseAudio 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 Lesser General Public License + along with PulseAudio; if not, see . +***/ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include + +#include +#include +#include + +#include "pulse_blocking_stream.h" + +struct pa_blocking { + pa_threaded_mainloop *mainloop; + pa_context *context; + pa_stream *stream; + pa_stream_direction_t direction; + + const void *read_data; + size_t read_index, read_length; + size_t channels; + + int operation_success; +}; + +#define CHECK_VALIDITY_RETURN_ANY(rerror, expression, error, ret) \ + do { \ + if (!(expression)) { \ + if (rerror) \ + *(rerror) = error; \ + return (ret); \ + } \ + } while(false); + +#define CHECK_SUCCESS_GOTO(p, rerror, expression, label) \ + do { \ + if (!(expression)) { \ + if (rerror) \ + *(rerror) = pa_context_errno((p)->context); \ + goto label; \ + } \ + } while(false); + +#define CHECK_DEAD_GOTO(p, rerror, label) \ + do { \ + if (!(p)->context || !PA_CONTEXT_IS_GOOD(pa_context_get_state((p)->context)) || \ + !(p)->stream || !PA_STREAM_IS_GOOD(pa_stream_get_state((p)->stream))) { \ + if (((p)->context && pa_context_get_state((p)->context) == PA_CONTEXT_FAILED) || \ + ((p)->stream && pa_stream_get_state((p)->stream) == PA_STREAM_FAILED)) { \ + if (rerror) \ + *(rerror) = pa_context_errno((p)->context); \ + } else \ + if (rerror) \ + *(rerror) = PA_ERR_BADSTATE; \ + goto label; \ + } \ + } while(false); + +static void context_state_cb(pa_context *c, void *userdata) { + pa_blocking *p = userdata; + assert(c); + assert(p); + + switch (pa_context_get_state(c)) { + case PA_CONTEXT_READY: + case PA_CONTEXT_TERMINATED: + case PA_CONTEXT_FAILED: + pa_threaded_mainloop_signal(p->mainloop, 0); + break; + + case PA_CONTEXT_UNCONNECTED: + case PA_CONTEXT_CONNECTING: + case PA_CONTEXT_AUTHORIZING: + case PA_CONTEXT_SETTING_NAME: + break; + } +} + +static void success_context_cb(pa_context *c, int success, void *userdata) { + pa_blocking *p = userdata; + assert(c); + assert(p); + + p->operation_success = success; + pa_threaded_mainloop_signal(p->mainloop, 0); +} + +static void stream_state_cb(pa_stream *s, void * userdata) { + pa_blocking *p = userdata; + assert(s); + assert(p); + + switch (pa_stream_get_state(s)) { + + case PA_STREAM_READY: + case PA_STREAM_FAILED: + case PA_STREAM_TERMINATED: + pa_threaded_mainloop_signal(p->mainloop, 0); + break; + + case PA_STREAM_UNCONNECTED: + case PA_STREAM_CREATING: + break; + } +} + +static void stream_request_cb(pa_stream *s, size_t length, void *userdata) { + pa_blocking *p = userdata; + assert(p); + + pa_threaded_mainloop_signal(p->mainloop, 0); +} + +static void stream_latency_update_cb(pa_stream *s, void *userdata) { + pa_blocking *p = userdata; + + assert(p); + + pa_threaded_mainloop_signal(p->mainloop, 0); +} + +pa_blocking* pa_blocking_new( + const char *server, + const char *name, + pa_stream_direction_t dir, + const char *dev, + const char *stream_name, + const pa_sample_spec *ss, + const pa_channel_map *map, + const pa_buffer_attr *attr, + int *rerror) { + + pa_blocking *p; + int error = PA_ERR_INTERNAL, r; + + CHECK_VALIDITY_RETURN_ANY(rerror, !server || *server, PA_ERR_INVALID, NULL); + CHECK_VALIDITY_RETURN_ANY(rerror, dir == PA_STREAM_PLAYBACK || dir == PA_STREAM_RECORD, PA_ERR_INVALID, NULL); + CHECK_VALIDITY_RETURN_ANY(rerror, !dev || *dev, PA_ERR_INVALID, NULL); + CHECK_VALIDITY_RETURN_ANY(rerror, ss && pa_sample_spec_valid(ss), PA_ERR_INVALID, NULL); + CHECK_VALIDITY_RETURN_ANY(rerror, !map || (pa_channel_map_valid(map) && map->channels == ss->channels), PA_ERR_INVALID, NULL) + + p = pa_xnew0(pa_blocking, 1); + p->direction = dir; + p->channels = ss->channels; + + if (!(p->mainloop = pa_threaded_mainloop_new())) + goto fail; + + if (!(p->context = pa_context_new(pa_threaded_mainloop_get_api(p->mainloop), name))) + goto fail; + + pa_context_set_state_callback(p->context, context_state_cb, p); + + if (pa_context_connect(p->context, server, 0, NULL) < 0) { + error = pa_context_errno(p->context); + goto fail; + } + + pa_threaded_mainloop_lock(p->mainloop); + + if (pa_threaded_mainloop_start(p->mainloop) < 0) + goto unlock_and_fail; + + for (;;) { + pa_context_state_t state; + + state = pa_context_get_state(p->context); + + if (state == PA_CONTEXT_READY) + break; + + if (!PA_CONTEXT_IS_GOOD(state)) { + error = pa_context_errno(p->context); + goto unlock_and_fail; + } + + /* Wait until the context is ready */ + pa_threaded_mainloop_wait(p->mainloop); + } + + if (!(p->stream = pa_stream_new(p->context, stream_name, ss, map))) { + error = pa_context_errno(p->context); + goto unlock_and_fail; + } + + pa_stream_set_state_callback(p->stream, stream_state_cb, p); + pa_stream_set_read_callback(p->stream, stream_request_cb, p); + pa_stream_set_write_callback(p->stream, stream_request_cb, p); + pa_stream_set_latency_update_callback(p->stream, stream_latency_update_cb, p); + + if (dir == PA_STREAM_PLAYBACK) + r = pa_stream_connect_playback(p->stream, dev, attr, + PA_STREAM_INTERPOLATE_TIMING + |PA_STREAM_ADJUST_LATENCY + |PA_STREAM_AUTO_TIMING_UPDATE, NULL, NULL); + else + r = pa_stream_connect_record(p->stream, dev, attr, + PA_STREAM_INTERPOLATE_TIMING + |PA_STREAM_ADJUST_LATENCY + |PA_STREAM_AUTO_TIMING_UPDATE); + + if (r < 0) { + error = pa_context_errno(p->context); + goto unlock_and_fail; + } + + for (;;) { + pa_stream_state_t state; + + state = pa_stream_get_state(p->stream); + + if (state == PA_STREAM_READY) + break; + + if (!PA_STREAM_IS_GOOD(state)) { + error = pa_context_errno(p->context); + goto unlock_and_fail; + } + + /* Wait until the stream is ready */ + pa_threaded_mainloop_wait(p->mainloop); + } + + pa_threaded_mainloop_unlock(p->mainloop); + + return p; + +unlock_and_fail: + pa_threaded_mainloop_unlock(p->mainloop); + +fail: + if (rerror) + *rerror = error; + pa_blocking_free(p); + return NULL; +} + +void pa_blocking_free(pa_blocking *s) { + assert(s); + + if (s->mainloop) + pa_threaded_mainloop_stop(s->mainloop); + + if (s->stream) + pa_stream_unref(s->stream); + + if (s->context) { + pa_context_disconnect(s->context); + pa_context_unref(s->context); + } + + if (s->mainloop) + pa_threaded_mainloop_free(s->mainloop); + + pa_xfree(s); +} + +int pa_blocking_write(pa_blocking *p, const void*data, size_t length, int *rerror) { + assert(p); + + CHECK_VALIDITY_RETURN_ANY(rerror, p->direction == PA_STREAM_PLAYBACK, PA_ERR_BADSTATE, -1); + CHECK_VALIDITY_RETURN_ANY(rerror, data, PA_ERR_INVALID, -1); + CHECK_VALIDITY_RETURN_ANY(rerror, length > 0, PA_ERR_INVALID, -1); + + pa_threaded_mainloop_lock(p->mainloop); + + CHECK_DEAD_GOTO(p, rerror, unlock_and_fail); + + while (length > 0) { + size_t l; + int r; + + while (!(l = pa_stream_writable_size(p->stream))) { + pa_threaded_mainloop_wait(p->mainloop); + CHECK_DEAD_GOTO(p, rerror, unlock_and_fail); + } + + CHECK_SUCCESS_GOTO(p, rerror, l != (size_t) -1, unlock_and_fail); + + if (l > length) + l = length; + + r = pa_stream_write(p->stream, data, l, NULL, 0LL, PA_SEEK_RELATIVE); + CHECK_SUCCESS_GOTO(p, rerror, r >= 0, unlock_and_fail); + + data = (const uint8_t*) data + l; + length -= l; + } + + pa_threaded_mainloop_unlock(p->mainloop); + return 0; + +unlock_and_fail: + pa_threaded_mainloop_unlock(p->mainloop); + return -1; +} + +int pa_blocking_read(pa_blocking *p, void*data, size_t length, int *rerror) { + assert(p); + + CHECK_VALIDITY_RETURN_ANY(rerror, p->direction == PA_STREAM_RECORD, PA_ERR_BADSTATE, -1); + CHECK_VALIDITY_RETURN_ANY(rerror, data, PA_ERR_INVALID, -1); + CHECK_VALIDITY_RETURN_ANY(rerror, length > 0, PA_ERR_INVALID, -1); + + pa_threaded_mainloop_lock(p->mainloop); + + CHECK_DEAD_GOTO(p, rerror, unlock_and_fail); + + while (length > 0) { + size_t l; + + while (!p->read_data) { + int r; + + r = pa_stream_peek(p->stream, &p->read_data, &p->read_length); + CHECK_SUCCESS_GOTO(p, rerror, r == 0, unlock_and_fail); + + if (p->read_length <= 0) { + pa_threaded_mainloop_wait(p->mainloop); + CHECK_DEAD_GOTO(p, rerror, unlock_and_fail); + } else if (!p->read_data) { + /* There's a hole in the stream, skip it. We could generate + * silence, but that wouldn't work for compressed streams. */ + r = pa_stream_drop(p->stream); + CHECK_SUCCESS_GOTO(p, rerror, r == 0, unlock_and_fail); + } else + p->read_index = 0; + } + + l = p->read_length < length ? p->read_length : length; + memcpy(data, (const uint8_t*) p->read_data+p->read_index, l); + + data = (uint8_t*) data + l; + length -= l; + + p->read_index += l; + p->read_length -= l; + + if (!p->read_length) { + int r; + + r = pa_stream_drop(p->stream); + p->read_data = NULL; + p->read_length = 0; + p->read_index = 0; + + CHECK_SUCCESS_GOTO(p, rerror, r == 0, unlock_and_fail); + } + } + + pa_threaded_mainloop_unlock(p->mainloop); + return 0; + +unlock_and_fail: + pa_threaded_mainloop_unlock(p->mainloop); + return -1; +} + +static void success_cb(pa_stream *s, int success, void *userdata) { + pa_blocking *p = userdata; + + assert(s); + assert(p); + + p->operation_success = success; + pa_threaded_mainloop_signal(p->mainloop, 0); +} + +int pa_blocking_drain(pa_blocking *p, int *rerror) { + pa_operation *o = NULL; + + assert(p); + + CHECK_VALIDITY_RETURN_ANY(rerror, p->direction == PA_STREAM_PLAYBACK, PA_ERR_BADSTATE, -1); + + pa_threaded_mainloop_lock(p->mainloop); + CHECK_DEAD_GOTO(p, rerror, unlock_and_fail); + + o = pa_stream_drain(p->stream, success_cb, p); + CHECK_SUCCESS_GOTO(p, rerror, o, unlock_and_fail); + + p->operation_success = 0; + while (pa_operation_get_state(o) == PA_OPERATION_RUNNING) { + pa_threaded_mainloop_wait(p->mainloop); + CHECK_DEAD_GOTO(p, rerror, unlock_and_fail); + } + CHECK_SUCCESS_GOTO(p, rerror, p->operation_success, unlock_and_fail); + + pa_operation_unref(o); + pa_threaded_mainloop_unlock(p->mainloop); + + return 0; + +unlock_and_fail: + + if (o) { + pa_operation_cancel(o); + pa_operation_unref(o); + } + + pa_threaded_mainloop_unlock(p->mainloop); + return -1; +} + +int pa_blocking_flush(pa_blocking *p, int *rerror) { + pa_operation *o = NULL; + + assert(p); + + pa_threaded_mainloop_lock(p->mainloop); + CHECK_DEAD_GOTO(p, rerror, unlock_and_fail); + + o = pa_stream_flush(p->stream, success_cb, p); + CHECK_SUCCESS_GOTO(p, rerror, o, unlock_and_fail); + + p->operation_success = 0; + while (pa_operation_get_state(o) == PA_OPERATION_RUNNING) { + pa_threaded_mainloop_wait(p->mainloop); + CHECK_DEAD_GOTO(p, rerror, unlock_and_fail); + } + CHECK_SUCCESS_GOTO(p, rerror, p->operation_success, unlock_and_fail); + + pa_operation_unref(o); + pa_threaded_mainloop_unlock(p->mainloop); + + return 0; + +unlock_and_fail: + + if (o) { + pa_operation_cancel(o); + pa_operation_unref(o); + } + + pa_threaded_mainloop_unlock(p->mainloop); + return -1; +} + +pa_usec_t pa_blocking_get_latency(pa_blocking *p, int *rerror) { + pa_usec_t t; + + assert(p); + + pa_threaded_mainloop_lock(p->mainloop); + + for (;;) { + int negative; + + CHECK_DEAD_GOTO(p, rerror, unlock_and_fail); + + if (pa_stream_get_latency(p->stream, &t, &negative) >= 0) { + pa_usec_t extra = 0; + + if (p->direction == PA_STREAM_RECORD) + extra = pa_bytes_to_usec(p->read_length, pa_stream_get_sample_spec(p->stream)); + + if (negative) { + if (extra > t) + t = extra - t; + else + t = 0; + } else + t += extra; + + break; + } + + CHECK_SUCCESS_GOTO(p, rerror, pa_context_errno(p->context) == PA_ERR_NODATA, unlock_and_fail); + + /* Wait until latency data is available again */ + pa_threaded_mainloop_wait(p->mainloop); + } + + pa_threaded_mainloop_unlock(p->mainloop); + + return t; + +unlock_and_fail: + + pa_threaded_mainloop_unlock(p->mainloop); + return (pa_usec_t) -1; +} + +int pa_blocking_set_volume(pa_blocking *p, int volume, int *rerror) { + pa_operation *o = NULL; + pa_stream *s = NULL; + uint32_t idx; + pa_cvolume cv; + pa_volume_t v; + + assert(p); + + CHECK_VALIDITY_RETURN_ANY(rerror, p->direction == PA_STREAM_PLAYBACK, PA_ERR_BADSTATE, -1); + CHECK_VALIDITY_RETURN_ANY(rerror, volume >= 0, PA_ERR_INVALID, -1); + CHECK_VALIDITY_RETURN_ANY(rerror, volume <= 65535, PA_ERR_INVALID, -1); + + pa_threaded_mainloop_lock(p->mainloop); + CHECK_DEAD_GOTO(p, rerror, unlock_and_fail); + + CHECK_SUCCESS_GOTO(p, rerror, ((idx = pa_stream_get_index (p->stream)) != PA_INVALID_INDEX), unlock_and_fail); + + s = p->stream; + assert(s); + pa_cvolume_set(&cv, p->channels, volume); + + o = pa_context_set_sink_input_volume (p->context, idx, &cv, success_context_cb, p); + CHECK_SUCCESS_GOTO(p, rerror, o, unlock_and_fail); + + p->operation_success = 0; + while (pa_operation_get_state(o) == PA_OPERATION_RUNNING) { + pa_threaded_mainloop_wait(p->mainloop); + CHECK_DEAD_GOTO(p, rerror, unlock_and_fail); + } + CHECK_SUCCESS_GOTO(p, rerror, p->operation_success, unlock_and_fail); + + pa_operation_unref(o); + pa_threaded_mainloop_unlock(p->mainloop); + + return 0; + +unlock_and_fail: + + if (o) { + pa_operation_cancel(o); + pa_operation_unref(o); + } + + pa_threaded_mainloop_unlock(p->mainloop); + return -1; +} diff --git a/src/contrib/pulseout/pulse_blocking_stream.h b/src/contrib/pulseout/pulse_blocking_stream.h new file mode 100644 index 000000000..4ec4542d1 --- /dev/null +++ b/src/contrib/pulseout/pulse_blocking_stream.h @@ -0,0 +1,82 @@ +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2006 Pierre Ossman for Cendio AB + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation; either version 2.1 of the License, + or (at your option) any later version. + + PulseAudio 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 Lesser General Public License + along with PulseAudio; if not, see . +***/ + +#pragma once + +#include +#include + +#include +#include +#include +#include +#include + +PA_C_DECL_BEGIN + +/** \struct pa_blocking + * An opaque simple connection object */ +typedef struct pa_blocking pa_blocking; + +/** Create a new connection to the server. */ +pa_blocking* pa_blocking_new( + const char *server, /**< Server name, or NULL for default */ + const char *name, /**< A descriptive name for this client (application name, ...) */ + pa_stream_direction_t dir, /**< Open this stream for recording or playback? */ + const char *dev, /**< Sink (resp. source) name, or NULL for default */ + const char *stream_name, /**< A descriptive name for this stream (application name, song title, ...) */ + const pa_sample_spec *ss, /**< The sample type to use */ + const pa_channel_map *map, /**< The channel map to use, or NULL for default */ + const pa_buffer_attr *attr, /**< Buffering attributes, or NULL for default */ + int *error /**< A pointer where the error code is stored when the routine returns NULL. It is OK to pass NULL here. */ + ); + +/** Close and free the connection to the server. The connection object becomes invalid when this is called. */ +void pa_blocking_free(pa_blocking *s); + +/** Write some data to the server. */ +int pa_blocking_write(pa_blocking *s, const void *data, size_t bytes, int *error); + +/** Wait until all data already written is played by the daemon. */ +int pa_blocking_drain(pa_blocking *s, int *error); + +/** Read some data from the server. This function blocks until \a bytes amount + * of data has been received from the server, or until an error occurs. + * Returns a negative value on failure. */ +int pa_blocking_read( + pa_blocking *s, /**< The connection object. */ + void *data, /**< A pointer to a buffer. */ + size_t bytes, /**< The number of bytes to read. */ + int *error + /**< A pointer where the error code is stored when the function returns + * a negative value. It is OK to pass NULL here. */ + ); + +/** Return the playback or record latency. */ +pa_usec_t pa_blocking_get_latency(pa_blocking *s, int *error); + +/** Flush the playback or record buffer. This discards any audio in the buffer. */ +int pa_blocking_flush(pa_blocking *s, int *error); + +/** Set the output volume of the stream. */ +int pa_blocking_set_volume(pa_blocking *p, int volume, int *rerror); + +PA_C_DECL_END + diff --git a/src/contrib/pulseout/pulseout_plugin.cpp b/src/contrib/pulseout/pulseout_plugin.cpp new file mode 100755 index 000000000..c092474b8 --- /dev/null +++ b/src/contrib/pulseout/pulseout_plugin.cpp @@ -0,0 +1,53 @@ +////////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2007-2016 musikcube team +// +// All rights reserved. +// +// 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. +// +// * Neither the name of the author nor the names of other contributors may +// be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// 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 COPYRIGHT OWNER 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. +// +////////////////////////////////////////////////////////////////////////////// + +#include "pch.h" +#include +#include +#include "PulseOut.h" + +class PulseOutPlugin : public musik::core::sdk::IPlugin { + virtual void Destroy() { delete this; }; + virtual const char* Name() { return "PulseAudio IOutput plugin"; } + virtual const char* Version() { return "0.1"; } + virtual const char* Author() { return "clangen"; } +}; + +extern "C" musik::core::sdk::IPlugin* GetPlugin() { + return new PulseOutPlugin(); +} + +extern "C" musik::core::sdk::IOutput* GetAudioOutput() { + return new PulseOut(); +}