Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/extern
diff options
context:
space:
mode:
authorJörg Müller <nexyon@gmail.com>2021-03-02 19:29:18 +0300
committerJörg Müller <nexyon@gmail.com>2021-03-17 01:21:45 +0300
commitd33339ebf44855b62200a2784c046792d78c75bc (patch)
tree425a1d78d6c40f8b188e499cf0db2bc29e8f2d32 /extern
parent7b8fc307dc4ff608fc68e2decee45bae59f0c7c9 (diff)
Audaspace: add support for PulseAudio on Linux
This adds PulseAudio as audio backend on Linux. PulseAudio is the main audio engine used on most, if not all, Linux distributions today. Ref T86590
Diffstat (limited to 'extern')
-rw-r--r--extern/audaspace/CMakeLists.txt55
-rw-r--r--extern/audaspace/blender_config.cmake3
-rw-r--r--extern/audaspace/plugins/pulseaudio/PulseAudioDevice.cpp282
-rw-r--r--extern/audaspace/plugins/pulseaudio/PulseAudioDevice.h105
-rw-r--r--extern/audaspace/plugins/pulseaudio/PulseAudioLibrary.cpp59
-rw-r--r--extern/audaspace/plugins/pulseaudio/PulseAudioLibrary.h46
-rw-r--r--extern/audaspace/plugins/pulseaudio/PulseAudioSymbols.h41
7 files changed, 591 insertions, 0 deletions
diff --git a/extern/audaspace/CMakeLists.txt b/extern/audaspace/CMakeLists.txt
index 996d4b27911..92b1cbcb290 100644
--- a/extern/audaspace/CMakeLists.txt
+++ b/extern/audaspace/CMakeLists.txt
@@ -285,6 +285,10 @@ if(AUDASPACE_STANDALONE)
option(WITH_SDL "Build With SDL" TRUE)
option(WITH_STRICT_DEPENDENCIES "Error and abort instead of warning if a library is not found." FALSE)
+ if(NOT WIN32 AND NOT APPLE)
+ option(WITH_PULSEAUDIO "Build With PulseAudio" TRUE)
+ endif()
+
if(WITH_STRICT_DEPENDENCIES)
set(PACKAGE_OPTION REQUIRED)
endif()
@@ -306,10 +310,12 @@ if(AUDASPACE_STANDALONE)
cmake_dependent_option(PLUGIN_JACK "Build JACK Plugin" TRUE "WITH_JACK;SHARED_LIBRARY" FALSE)
cmake_dependent_option(PLUGIN_LIBSNDFILE "Build LibSndFile Plugin" TRUE "WITH_LIBSNDFILE;SHARED_LIBRARY" FALSE)
cmake_dependent_option(PLUGIN_OPENAL "Build OpenAL Plugin" TRUE "WITH_OPENAL;SHARED_LIBRARY" FALSE)
+ cmake_dependent_option(PLUGIN_PULSEAUDIO "Build PulseAudio Plugin" TRUE "WITH_PULSEAUDIO;SHARED_LIBRARY" FALSE)
cmake_dependent_option(PLUGIN_SDL "Build SDL Plugin" TRUE "WITH_SDL;SHARED_LIBRARY" FALSE)
cmake_dependent_option(WITH_PYTHON_MODULE "Build Python Module" TRUE "WITH_PYTHON" FALSE)
cmake_dependent_option(USE_SDL2 "Use SDL2 instead of 1 if available" TRUE "WITH_SDL" FALSE)
cmake_dependent_option(DYNLOAD_JACK "Dynamically load JACK" FALSE "WITH_JACK" FALSE)
+ cmake_dependent_option(DYNLOAD_PULSEAUDIO "Dynamically load PulseAudio" FALSE "WITH_PULSEAUDIO" FALSE)
cmake_dependent_option(WITH_BINDING_DOCS "Build C/Python HTML Documentation with Sphinx" TRUE "WITH_PYTHON_MODULE" FALSE)
endif()
@@ -598,6 +604,42 @@ if(WITH_OPENAL)
endif()
endif()
+# PulseAudio
+if(WITH_PULSEAUDIO)
+ if(AUDASPACE_STANDALONE)
+ find_package(LibPulse ${PACKAGE_OPTION})
+ endif()
+
+ if(LIBPULSE_FOUND)
+ set(PULSEAUDIO_SRC
+ plugins/pulseaudio/PulseAudioDevice.cpp
+ plugins/pulseaudio/PulseAudioLibrary.cpp
+ )
+ set(PULSEAUDIO_HDR
+ plugins/pulseaudio/PulseAudioDevice.h
+ plugins/pulseaudio/PulseAudioLibrary.h
+ plugins/pulseaudio/PulseAudioSymbols.h
+ )
+
+ if(DYNLOAD_PULSEAUDIO)
+ add_definitions(-DDYNLOAD_PULSEAUDIO)
+ endif()
+
+ if(NOT PLUGIN_PULSEAUDIO)
+ list(APPEND INCLUDE ${LIBPULSE_INCLUDE_DIR})
+ if(NOT DYNLOAD_PULSEAUDIO)
+ list(APPEND LIBRARIES ${LIBPULSE_LIBRARY})
+ endif()
+ list(APPEND SRC ${PULSEAUDIO_SRC})
+ list(APPEND HDR ${PULSEAUDIO_HDR})
+ list(APPEND STATIC_PLUGINS PulseAudioDevice)
+ endif()
+ else()
+ set(WITH_PULSEAUDIO FALSE CACHE BOOL "Build With PulseAudio" FORCE)
+ message(WARNING "PulseAudio not found, plugin will not be built.")
+ endif()
+endif()
+
# Python
if(WITH_PYTHON)
if(AUDASPACE_STANDALONE)
@@ -797,6 +839,19 @@ if(WITH_OPENAL AND PLUGIN_OPENAL)
install(TARGETS audopenal DESTINATION ${DEFAULT_PLUGIN_PATH})
endif()
+if(WITH_PULSEAUDIO AND PLUGIN_PULSEAUDIO)
+ add_definitions(-DPULSEAUDIO_PLUGIN)
+ include_directories(${INCLUDE} ${LIBPULSE_INCLUDE_DIR})
+ add_library(audpulseaudio SHARED ${PULSEAUDIO_SRC} ${PULSEAUDIO_HDR} ${HDR})
+ set_target_properties(audpulseaudio PROPERTIES SOVERSION ${AUDASPACE_VERSION})
+ if(DYNLOAD_PULSEAUDIO)
+ target_link_libraries(audpulseaudio audaspace)
+ else()
+ target_link_libraries(audpulseaudio audaspace ${LIBPULSE_LIBRARY})
+ endif()
+ install(TARGETS audpulseaudio DESTINATION ${DEFAULT_PLUGIN_PATH})
+endif()
+
if(WITH_SDL AND PLUGIN_SDL)
add_definitions(-DSDL_PLUGIN)
include_directories(${INCLUDE} ${SDL_INCLUDE_DIR})
diff --git a/extern/audaspace/blender_config.cmake b/extern/audaspace/blender_config.cmake
index 12810e2b044..22e6564ad89 100644
--- a/extern/audaspace/blender_config.cmake
+++ b/extern/audaspace/blender_config.cmake
@@ -14,15 +14,18 @@ set(PLUGIN_FFMPEG FALSE) # "Build FFMPEG Plugin"
set(PLUGIN_JACK FALSE) # "Build JACK Plugin"
set(PLUGIN_LIBSNDFILE FALSE) # "Build LibSndFile Plugin"
set(PLUGIN_OPENAL FALSE) # "Build OpenAL Plugin"
+set(PLUGIN_PULSEAUDIO FALSE) # "Build PulseAudio Plugin"
set(PLUGIN_SDL FALSE) # "Build SDL Plugin"
set(WITH_PYTHON_MODULE FALSE) # "Build Python Module"
set(DYNLOAD_JACK ${WITH_JACK_DYNLOAD}) # "Dynamically load JACK"
+set(DYNLOAD_PULSEAUDIO ${WITH_PULSEAUDIO_DYNLOAD}) # "Dynamically load PulseAudio"
set(WITH_BINDING_DOCS FALSE) # "Build C/Python HTML Documentation with Sphinx"
set(DEFAULT_PLUGIN_PATH "plugins") # "Default plugin installation and loading path."
set(FFMPEG_FOUND ${WITH_CODEC_FFMPEG})
set(JACK_FOUND ${WITH_JACK})
set(LIBSNDFILE_FOUND ${WITH_CODEC_SNDFILE})
set(OPENAL_FOUND ${WITH_OPENAL})
+set(LIBPULSE_FOUND ${WITH_PULSEAUDIO})
set(PYTHONLIBS_FOUND TRUE)
set(NUMPY_FOUND ${WITH_PYTHON_NUMPY})
set(NUMPY_INCLUDE_DIRS ${PYTHON_NUMPY_INCLUDE_DIRS})
diff --git a/extern/audaspace/plugins/pulseaudio/PulseAudioDevice.cpp b/extern/audaspace/plugins/pulseaudio/PulseAudioDevice.cpp
new file mode 100644
index 00000000000..0a50d5db2c7
--- /dev/null
+++ b/extern/audaspace/plugins/pulseaudio/PulseAudioDevice.cpp
@@ -0,0 +1,282 @@
+/*******************************************************************************
+ * Copyright 2009-2016 Jörg Müller
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ ******************************************************************************/
+
+#include "PulseAudioDevice.h"
+#include "PulseAudioLibrary.h"
+#include "devices/DeviceManager.h"
+#include "devices/IDeviceFactory.h"
+#include "Exception.h"
+#include "IReader.h"
+
+AUD_NAMESPACE_BEGIN
+
+void PulseAudioDevice::PulseAudio_state_callback(pa_context *context, void *data)
+{
+ PulseAudioDevice* device = (PulseAudioDevice*)data;
+
+ device->m_state = AUD_pa_context_get_state(context);
+
+ AUD_pa_threaded_mainloop_signal(device->m_mainloop, 0);
+}
+
+void PulseAudioDevice::PulseAudio_request(pa_stream *stream, size_t num_bytes, void *data)
+{
+ PulseAudioDevice* device = (PulseAudioDevice*)data;
+
+ void* buffer;
+
+ AUD_pa_stream_begin_write(stream, &buffer, &num_bytes);
+
+ device->mix((data_t*)buffer, num_bytes / AUD_DEVICE_SAMPLE_SIZE(device->m_specs));
+
+ AUD_pa_stream_write(stream, buffer, num_bytes, nullptr, 0, PA_SEEK_RELATIVE);
+}
+
+void PulseAudioDevice::PulseAudio_underflow(pa_stream *stream, void *data)
+{
+ PulseAudioDevice* device = (PulseAudioDevice*)data;
+
+ DeviceSpecs specs = device->getSpecs();
+
+ if(++device->m_underflows > 4 && device->m_buffersize < AUD_DEVICE_SAMPLE_SIZE(specs) * specs.rate * 2)
+ {
+ device->m_buffersize <<= 1;
+ device->m_underflows = 0;
+
+ pa_buffer_attr buffer_attr;
+
+ buffer_attr.fragsize = -1U;
+ buffer_attr.maxlength = -1U;
+ buffer_attr.minreq = -1U;
+ buffer_attr.prebuf = -1U;
+ buffer_attr.tlength = device->m_buffersize;
+
+ AUD_pa_stream_set_buffer_attr(stream, &buffer_attr, nullptr, nullptr);
+ }
+}
+
+void PulseAudioDevice::playing(bool playing)
+{
+ m_playback = playing;
+
+ AUD_pa_stream_cork(m_stream, playing ? 0 : 1, nullptr, nullptr);
+}
+
+PulseAudioDevice::PulseAudioDevice(std::string name, DeviceSpecs specs, int buffersize) :
+ m_playback(false),
+ m_state(PA_CONTEXT_UNCONNECTED),
+ m_buffersize(buffersize),
+ m_underflows(0)
+{
+ m_mainloop = AUD_pa_threaded_mainloop_new();
+
+ AUD_pa_threaded_mainloop_lock(m_mainloop);
+
+ m_context = AUD_pa_context_new(AUD_pa_threaded_mainloop_get_api(m_mainloop), name.c_str());
+
+ if(!m_context)
+ {
+ AUD_pa_threaded_mainloop_unlock(m_mainloop);
+ AUD_pa_threaded_mainloop_free(m_mainloop);
+
+ AUD_THROW(DeviceException, "Could not connect to PulseAudio.");
+ }
+
+ AUD_pa_context_set_state_callback(m_context, PulseAudio_state_callback, this);
+
+ AUD_pa_context_connect(m_context, nullptr, PA_CONTEXT_NOFLAGS, nullptr);
+
+ AUD_pa_threaded_mainloop_start(m_mainloop);
+
+ while(m_state != PA_CONTEXT_READY)
+ {
+ switch(m_state)
+ {
+ case PA_CONTEXT_FAILED:
+ case PA_CONTEXT_TERMINATED:
+ AUD_pa_threaded_mainloop_unlock(m_mainloop);
+ AUD_pa_threaded_mainloop_stop(m_mainloop);
+
+ AUD_pa_context_disconnect(m_context);
+ AUD_pa_context_unref(m_context);
+
+ AUD_pa_threaded_mainloop_free(m_mainloop);
+
+ AUD_THROW(DeviceException, "Could not connect to PulseAudio.");
+ break;
+ default:
+ AUD_pa_threaded_mainloop_wait(m_mainloop);
+ break;
+ }
+ }
+
+ if(specs.channels == CHANNELS_INVALID)
+ specs.channels = CHANNELS_STEREO;
+ if(specs.format == FORMAT_INVALID)
+ specs.format = FORMAT_FLOAT32;
+ if(specs.rate == RATE_INVALID)
+ specs.rate = RATE_48000;
+
+ m_specs = specs;
+
+ pa_sample_spec sample_spec;
+
+ sample_spec.channels = specs.channels;
+ sample_spec.format = PA_SAMPLE_FLOAT32;
+ sample_spec.rate = specs.rate;
+
+ switch(m_specs.format)
+ {
+ case FORMAT_U8:
+ sample_spec.format = PA_SAMPLE_U8;
+ break;
+ case FORMAT_S16:
+ sample_spec.format = PA_SAMPLE_S16NE;
+ break;
+ case FORMAT_S24:
+ sample_spec.format = PA_SAMPLE_S24NE;
+ break;
+ case FORMAT_S32:
+ sample_spec.format = PA_SAMPLE_S32NE;
+ break;
+ case FORMAT_FLOAT32:
+ sample_spec.format = PA_SAMPLE_FLOAT32;
+ break;
+ case FORMAT_FLOAT64:
+ m_specs.format = FORMAT_FLOAT32;
+ break;
+ default:
+ break;
+ }
+
+ m_stream = AUD_pa_stream_new(m_context, "Playback", &sample_spec, nullptr);
+
+ if(!m_stream)
+ {
+ AUD_pa_threaded_mainloop_unlock(m_mainloop);
+ AUD_pa_threaded_mainloop_stop(m_mainloop);
+
+ AUD_pa_context_disconnect(m_context);
+ AUD_pa_context_unref(m_context);
+
+ AUD_pa_threaded_mainloop_free(m_mainloop);
+
+ AUD_THROW(DeviceException, "Could not create PulseAudio stream.");
+ }
+
+ AUD_pa_stream_set_write_callback(m_stream, PulseAudio_request, this);
+ AUD_pa_stream_set_underflow_callback(m_stream, PulseAudio_underflow, this);
+
+ pa_buffer_attr buffer_attr;
+
+ buffer_attr.fragsize = -1U;
+ buffer_attr.maxlength = -1U;
+ buffer_attr.minreq = -1U;
+ buffer_attr.prebuf = -1U;
+ buffer_attr.tlength = buffersize;
+
+ if(AUD_pa_stream_connect_playback(m_stream, nullptr, &buffer_attr, static_cast<pa_stream_flags_t>(PA_STREAM_INTERPOLATE_TIMING | PA_STREAM_ADJUST_LATENCY | PA_STREAM_AUTO_TIMING_UPDATE), nullptr, nullptr) < 0)
+ {
+ AUD_pa_threaded_mainloop_unlock(m_mainloop);
+ AUD_pa_threaded_mainloop_stop(m_mainloop);
+
+ AUD_pa_context_disconnect(m_context);
+ AUD_pa_context_unref(m_context);
+
+ AUD_pa_threaded_mainloop_free(m_mainloop);
+
+ AUD_THROW(DeviceException, "Could not connect PulseAudio stream.");
+ }
+
+ AUD_pa_threaded_mainloop_unlock(m_mainloop);
+
+ create();
+}
+
+PulseAudioDevice::~PulseAudioDevice()
+{
+ AUD_pa_threaded_mainloop_stop(m_mainloop);
+
+ AUD_pa_context_disconnect(m_context);
+ AUD_pa_context_unref(m_context);
+
+ AUD_pa_threaded_mainloop_free(m_mainloop);
+
+ destroy();
+}
+
+class PulseAudioDeviceFactory : public IDeviceFactory
+{
+private:
+ DeviceSpecs m_specs;
+ int m_buffersize;
+ std::string m_name;
+
+public:
+ PulseAudioDeviceFactory() :
+ m_buffersize(AUD_DEFAULT_BUFFER_SIZE),
+ m_name("Audaspace")
+ {
+ m_specs.format = FORMAT_FLOAT32;
+ m_specs.channels = CHANNELS_STEREO;
+ m_specs.rate = RATE_48000;
+ }
+
+ virtual std::shared_ptr<IDevice> openDevice()
+ {
+ return std::shared_ptr<IDevice>(new PulseAudioDevice(m_name, m_specs, m_buffersize));
+ }
+
+ virtual int getPriority()
+ {
+ return 1 << 15;
+ }
+
+ virtual void setSpecs(DeviceSpecs specs)
+ {
+ m_specs = specs;
+ }
+
+ virtual void setBufferSize(int buffersize)
+ {
+ m_buffersize = buffersize;
+ }
+
+ virtual void setName(std::string name)
+ {
+ m_name = name;
+ }
+};
+
+void PulseAudioDevice::registerPlugin()
+{
+ if(loadPulseAudio())
+ DeviceManager::registerDevice("PulseAudio", std::shared_ptr<IDeviceFactory>(new PulseAudioDeviceFactory));
+}
+
+#ifdef PULSEAUDIO_PLUGIN
+extern "C" AUD_PLUGIN_API void registerPlugin()
+{
+ PulseAudioDevice::registerPlugin();
+}
+
+extern "C" AUD_PLUGIN_API const char* getName()
+{
+ return "PulseAudio";
+}
+#endif
+
+AUD_NAMESPACE_END
diff --git a/extern/audaspace/plugins/pulseaudio/PulseAudioDevice.h b/extern/audaspace/plugins/pulseaudio/PulseAudioDevice.h
new file mode 100644
index 00000000000..9efae5128b1
--- /dev/null
+++ b/extern/audaspace/plugins/pulseaudio/PulseAudioDevice.h
@@ -0,0 +1,105 @@
+/*******************************************************************************
+ * Copyright 2009-2016 Jörg Müller
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ ******************************************************************************/
+
+#pragma once
+
+#ifdef PULSEAUDIO_PLUGIN
+#define AUD_BUILD_PLUGIN
+#endif
+
+/**
+ * @file PulseAudioDevice.h
+ * @ingroup plugin
+ * The PulseAudioDevice class.
+ */
+
+#include "devices/SoftwareDevice.h"
+
+#include <pulse/pulseaudio.h>
+
+AUD_NAMESPACE_BEGIN
+
+/**
+ * This device plays back through PulseAudio, the simple direct media layer.
+ */
+class AUD_PLUGIN_API PulseAudioDevice : public SoftwareDevice
+{
+private:
+ /**
+ * Whether there is currently playback.
+ */
+ volatile bool m_playback;
+
+ pa_threaded_mainloop* m_mainloop;
+ pa_context* m_context;
+ pa_stream* m_stream;
+ pa_context_state_t m_state;
+
+ int m_buffersize;
+ uint32_t m_underflows;
+
+ /**
+ * Reports the state of the PulseAudio server connection.
+ * \param context The PulseAudio context.
+ * \param data The PulseAudio device.
+ */
+ AUD_LOCAL static void PulseAudio_state_callback(pa_context* context, void* data);
+
+ /**
+ * Supplies the next samples to PulseAudio.
+ * \param stream The PulseAudio stream.
+ * \param num_bytes The length in bytes to be supplied.
+ * \param data The PulseAudio device.
+ */
+ AUD_LOCAL static void PulseAudio_request(pa_stream* stream, size_t num_bytes, void* data);
+
+ /**
+ * Reports an underflow from the PulseAudio server.
+ * Automatically adjusts the latency if this happens too often.
+ * @param stream The PulseAudio stream.
+ * \param data The PulseAudio device.
+ */
+ AUD_LOCAL static void PulseAudio_underflow(pa_stream* stream, void* data);
+
+ // delete copy constructor and operator=
+ PulseAudioDevice(const PulseAudioDevice&) = delete;
+ PulseAudioDevice& operator=(const PulseAudioDevice&) = delete;
+
+protected:
+ virtual void playing(bool playing);
+
+public:
+ /**
+ * Opens the PulseAudio audio device for playback.
+ * \param specs The wanted audio specification.
+ * \param buffersize The size of the internal buffer.
+ * \note The specification really used for opening the device may differ.
+ * \exception Exception Thrown if the audio device cannot be opened.
+ */
+ PulseAudioDevice(std::string name, DeviceSpecs specs, int buffersize = AUD_DEFAULT_BUFFER_SIZE);
+
+ /**
+ * Closes the PulseAudio audio device.
+ */
+ virtual ~PulseAudioDevice();
+
+ /**
+ * Registers this plugin.
+ */
+ static void registerPlugin();
+};
+
+AUD_NAMESPACE_END
diff --git a/extern/audaspace/plugins/pulseaudio/PulseAudioLibrary.cpp b/extern/audaspace/plugins/pulseaudio/PulseAudioLibrary.cpp
new file mode 100644
index 00000000000..66a0fae5ab6
--- /dev/null
+++ b/extern/audaspace/plugins/pulseaudio/PulseAudioLibrary.cpp
@@ -0,0 +1,59 @@
+/*******************************************************************************
+ * Copyright 2009-2016 Jörg Müller
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ ******************************************************************************/
+
+#define PULSEAUDIO_LIBRARY_IMPLEMENTATION
+
+#include <string>
+#include <array>
+
+#include "PulseAudioLibrary.h"
+
+#ifdef DYNLOAD_PULSEAUDIO
+#include "plugin/PluginManager.h"
+#endif
+
+AUD_NAMESPACE_BEGIN
+
+bool loadPulseAudio()
+{
+#ifdef DYNLOAD_PULSEAUDIO
+ std::array<const std::string, 2> names = {"libpulse.so", "libpulse.so.0"};
+
+ void* handle = nullptr;
+
+ for(auto& name : names)
+ {
+ handle = PluginManager::openLibrary(name);
+ if(handle)
+ break;
+ }
+
+ if (!handle)
+ return false;
+
+#define PULSEAUDIO_SYMBOL(sym) AUD_##sym = reinterpret_cast<decltype(&sym)>(PluginManager::lookupLibrary(handle, #sym))
+#else
+#define PULSEAUDIO_SYMBOL(sym) AUD_##sym = &sym
+#endif
+
+#include "PulseAudioSymbols.h"
+
+#undef PULSEAUDIO_SYMBOL
+
+ return AUD_pa_context_new != nullptr;
+}
+
+AUD_NAMESPACE_END
diff --git a/extern/audaspace/plugins/pulseaudio/PulseAudioLibrary.h b/extern/audaspace/plugins/pulseaudio/PulseAudioLibrary.h
new file mode 100644
index 00000000000..ae6c5951d11
--- /dev/null
+++ b/extern/audaspace/plugins/pulseaudio/PulseAudioLibrary.h
@@ -0,0 +1,46 @@
+/*******************************************************************************
+ * Copyright 2009-2016 Jörg Müller
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ ******************************************************************************/
+
+#pragma once
+
+#ifdef PULSEAUDIO_PLUGIN
+#define AUD_BUILD_PLUGIN
+#endif
+
+/**
+ * @file PulseAudioLibrary.h
+ * @ingroup plugin
+ */
+
+#include "Audaspace.h"
+
+#include <pulse/pulseaudio.h>
+
+AUD_NAMESPACE_BEGIN
+
+#ifdef PULSEAUDIO_LIBRARY_IMPLEMENTATION
+#define PULSEAUDIO_SYMBOL(sym) decltype(&sym) AUD_##sym
+#else
+#define PULSEAUDIO_SYMBOL(sym) extern decltype(&sym) AUD_##sym
+#endif
+
+#include "PulseAudioSymbols.h"
+
+#undef PULSEAUDIO_SYMBOL
+
+bool loadPulseAudio();
+
+AUD_NAMESPACE_END
diff --git a/extern/audaspace/plugins/pulseaudio/PulseAudioSymbols.h b/extern/audaspace/plugins/pulseaudio/PulseAudioSymbols.h
new file mode 100644
index 00000000000..9cefbc0c7e2
--- /dev/null
+++ b/extern/audaspace/plugins/pulseaudio/PulseAudioSymbols.h
@@ -0,0 +1,41 @@
+/*******************************************************************************
+ * Copyright 2009-2016 Jörg Müller
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ ******************************************************************************/
+
+PULSEAUDIO_SYMBOL(pa_context_connect);
+PULSEAUDIO_SYMBOL(pa_context_disconnect);
+PULSEAUDIO_SYMBOL(pa_context_get_state);
+PULSEAUDIO_SYMBOL(pa_context_new);
+PULSEAUDIO_SYMBOL(pa_context_set_state_callback);
+PULSEAUDIO_SYMBOL(pa_context_unref);
+
+PULSEAUDIO_SYMBOL(pa_stream_begin_write);
+PULSEAUDIO_SYMBOL(pa_stream_connect_playback);
+PULSEAUDIO_SYMBOL(pa_stream_cork);
+PULSEAUDIO_SYMBOL(pa_stream_new);
+PULSEAUDIO_SYMBOL(pa_stream_set_buffer_attr);
+PULSEAUDIO_SYMBOL(pa_stream_set_underflow_callback);
+PULSEAUDIO_SYMBOL(pa_stream_set_write_callback);
+PULSEAUDIO_SYMBOL(pa_stream_write);
+
+PULSEAUDIO_SYMBOL(pa_threaded_mainloop_free);
+PULSEAUDIO_SYMBOL(pa_threaded_mainloop_get_api);
+PULSEAUDIO_SYMBOL(pa_threaded_mainloop_lock);
+PULSEAUDIO_SYMBOL(pa_threaded_mainloop_new);
+PULSEAUDIO_SYMBOL(pa_threaded_mainloop_signal);
+PULSEAUDIO_SYMBOL(pa_threaded_mainloop_start);
+PULSEAUDIO_SYMBOL(pa_threaded_mainloop_stop);
+PULSEAUDIO_SYMBOL(pa_threaded_mainloop_unlock);
+PULSEAUDIO_SYMBOL(pa_threaded_mainloop_wait);