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
This commit is contained in:
Joerg Mueller 2021-03-02 17:29:18 +01:00
parent 7b8fc307dc
commit d33339ebf4
Notes: blender-bot 2023-05-31 04:43:10 +02:00
Referenced by issue #86590, Audaspace: Use platform native audio APIs
23 changed files with 728 additions and 2 deletions

View File

@ -311,6 +311,12 @@ endif()
if(UNIX AND NOT APPLE)
option(WITH_SDL_DYNLOAD "Enable runtime dynamic SDL libraries loading" OFF)
endif()
if(UNIX AND NOT APPLE)
option(WITH_PULSEAUDIO "Enable PulseAudio for audio support on Linux" ON)
option(WITH_PULSEAUDIO_DYNLOAD "Enable runtime dynamic PulseAudio libraries loading" OFF)
else()
set(WITH_PULSEAUDIO OFF)
endif()
# Compression
option(WITH_LZO "Enable fast LZO compression (used for pointcache)" ON)
@ -675,6 +681,7 @@ endif()
set_and_warn_dependency(WITH_AUDASPACE WITH_OPENAL OFF)
set_and_warn_dependency(WITH_AUDASPACE WITH_JACK OFF)
set_and_warn_dependency(WITH_AUDASPACE WITH_PULSEAUDIO OFF)
if(NOT WITH_SDL AND WITH_GHOST_SDL)
message(FATAL_ERROR "WITH_GHOST_SDL requires WITH_SDL")
@ -1937,6 +1944,8 @@ if(FIRST_RUN)
info_cfg_option(WITH_OPENAL)
info_cfg_option(WITH_SDL)
info_cfg_option(WITH_SDL_DYNLOAD)
info_cfg_option(WITH_PULSEAUDIO)
info_cfg_option(WITH_PULSEAUDIO_DYNLOAD)
info_cfg_text("Compression:")
info_cfg_option(WITH_LZMA)

View File

@ -51,7 +51,7 @@ ARGS=$( \
getopt \
-o s:i:t:h \
--long source:,install:,tmp:,info:,threads:,help,show-deps,no-sudo,no-build,no-confirm,\
with-all,with-opencollada,with-jack,with-embree,with-oidn,with-nanovdb,\
with-all,with-opencollada,with-jack,with-pulseaudio,with-embree,with-oidn,with-nanovdb,\
ver-ocio:,ver-oiio:,ver-llvm:,ver-osl:,ver-osd:,ver-openvdb:,ver-xr-openxr:,\
force-all,force-python,force-boost,force-tbb,\
force-ocio,force-openexr,force-oiio,force-llvm,force-osl,force-osd,force-openvdb,\
@ -157,6 +157,9 @@ ARGUMENTS_INFO="\"COMMAND LINE ARGUMENTS:
--with-jack
Install the jack libraries.
--with-pulseaudio
Install the pulseaudio libraries.
--ver-ocio=<ver>
Force version of OCIO library.
@ -721,6 +724,9 @@ while true; do
--with-jack)
WITH_JACK=true; shift; continue;
;;
--with-pulseaudio)
WITH_PULSEAUDIO=true; shift; continue;
;;
--ver-ocio)
OCIO_VERSION="$2"
OCIO_VERSION_MIN=$OCIO_VERSION
@ -985,6 +991,7 @@ fi
if [ "$WITH_ALL" = true ]; then
WITH_JACK=true
WITH_NANOVDB=true
WITH_PULSEAUDIO=true
fi
if [ "$WITH_NANOVDB" = true ]; then
@ -3877,6 +3884,10 @@ install_DEB() {
fi
fi
if [ "$WITH_PULSEAUDIO" = true ]; then
_packages="$_packages libpulse-dev"
fi
PRINT ""
install_packages_DEB $_packages
@ -4499,6 +4510,10 @@ install_RPM() {
_packages="$_packages jack-audio-connection-kit-devel"
fi
if [ "$WITH_PULSEAUDIO" = true ]; then
_packages="$_packages pulseaudio-libs-devel"
fi
PRINT ""
install_packages_RPM $_packages
@ -4542,6 +4557,10 @@ install_RPM() {
X264_USE=true
fi
if [ "$WITH_PULSEAUDIO" = true ]; then
_packages="$_packages libpulse-devel"
fi
if [ "$WITH_ALL" = true ]; then
PRINT ""
XVID_DEV="libxvidcore-devel"
@ -5073,6 +5092,10 @@ install_ARCH() {
_packages="$_packages jack2"
fi
if [ "$WITH_PULSEAUDIO" = true ]; then
_packages="$_packages libpulse"
fi
PRINT ""
install_packages_ARCH $_packages
@ -5888,6 +5911,14 @@ print_info() {
_buildargs="$_buildargs $_1 $_2"
fi
if [ "$WITH_PULSEAUDIO" = true ]; then
_1="-D WITH_PULSEAUDIO=ON"
_2="-D WITH_PULSEAUDIO_DYNLOAD=ON"
PRINT " $_1"
PRINT " $_2"
_buildargs="$_buildargs $_1 $_2"
fi
if [ "$ALEMBIC_SKIP" = false ]; then
_1="-D WITH_ALEMBIC=ON"
PRINT " $_1"

View File

@ -16,6 +16,7 @@ set(WITH_DOC_MANPAGE OFF CACHE BOOL "" FORCE)
# Options which are specific to Linux release builds only
set(WITH_JACK_DYNLOAD ON CACHE BOOL "" FORCE)
set(WITH_PULSEAUDIO_DYNLOAD ON CACHE BOOL "" FORCE)
set(WITH_SDL_DYNLOAD ON CACHE BOOL "" FORCE)
# ######## Release environment specific settings ########

View File

@ -0,0 +1,60 @@
# - Find PulseAudio library
# Find the native PulseAudio includes and library
# This module defines
# LIBPULSE_INCLUDE_DIRS, where to find pulse/pulseaudio.h, Set when
# LIBPULSE_INCLUDE_DIR is found.
# LIBPULSE_LIBRARIES, libraries to link against to use PulseAudio.
# LIBPULSE_ROOT_DIR, The base directory to search for PulseAudio.
# This can also be an environment variable.
# PULSE_FOUND, If false, do not try to use PulseAudio.
#
# also defined, but not for general use are
# LIBPULSE_LIBRARY, where to find the PulseAudio library.
#=============================================================================
# Copyright 2021 Blender Foundation.
#
# Distributed under the OSI-approved BSD 3-Clause License,
# see accompanying file BSD-3-Clause-license.txt for details.
#=============================================================================
# If LIBPULSE_ROOT_DIR was defined in the environment, use it.
IF(NOT LIBPULSE_ROOT_DIR AND NOT $ENV{LIBPULSE_ROOT_DIR} STREQUAL "")
SET(LIBPULSE_ROOT_DIR $ENV{LIBPULSE_ROOT_DIR})
ENDIF()
SET(_pulse_SEARCH_DIRS
${LIBPULSE_ROOT_DIR}
)
FIND_PATH(LIBPULSE_INCLUDE_DIR pulse/pulseaudio.h
HINTS
${_pulse_SEARCH_DIRS}
PATH_SUFFIXES
include
)
FIND_LIBRARY(LIBPULSE_LIBRARY
NAMES
pulse
HINTS
${_pulse_SEARCH_DIRS}
PATH_SUFFIXES
lib64 lib
)
# handle the QUIETLY and REQUIRED arguments and set PULSE_FOUND to TRUE if
# all listed variables are TRUE
INCLUDE(FindPackageHandleStandardArgs)
FIND_PACKAGE_HANDLE_STANDARD_ARGS(Pulse DEFAULT_MSG
LIBPULSE_LIBRARY LIBPULSE_INCLUDE_DIR)
IF(PULSE_FOUND)
SET(LIBPULSE_LIBRARIES ${LIBPULSE_LIBRARY})
SET(LIBPULSE_INCLUDE_DIRS ${LIBPULSE_INCLUDE_DIR})
ENDIF()
MARK_AS_ADVANCED(
LIBPULSE_INCLUDE_DIR
LIBPULSE_LIBRARY
)

View File

@ -65,6 +65,7 @@ endif()
if(UNIX AND NOT APPLE)
set(WITH_DOC_MANPAGE ON CACHE BOOL "" FORCE)
set(WITH_GHOST_XDND ON CACHE BOOL "" FORCE)
set(WITH_PULSEAUDIO ON CACHE BOOL "" FORCE)
set(WITH_X11_XINPUT ON CACHE BOOL "" FORCE)
set(WITH_X11_XF86VMODE ON CACHE BOOL "" FORCE)
endif()

View File

@ -11,6 +11,7 @@ set(WITH_HEADLESS ON CACHE BOOL "" FORCE)
# so the python module doesn't hold the audio device and loads quickly.
set(WITH_AUDASPACE OFF CACHE BOOL "" FORCE)
set(WITH_JACK OFF CACHE BOOL "" FORCE)
set(WITH_PULSEAUDIO OFF CACHE BOOL "" FORCE)
set(WITH_SDL OFF CACHE BOOL "" FORCE)
set(WITH_OPENAL OFF CACHE BOOL "" FORCE)
set(WITH_CODEC_FFMPEG OFF CACHE BOOL "" FORCE)

View File

@ -54,6 +54,7 @@ set(WITH_OPENSUBDIV OFF CACHE BOOL "" FORCE)
set(WITH_OPENVDB OFF CACHE BOOL "" FORCE)
set(WITH_POTRACE OFF CACHE BOOL "" FORCE)
set(WITH_PUGIXML OFF CACHE BOOL "" FORCE)
set(WITH_PULSEAUDIO OFF CACHE BOOL "" FORCE)
set(WITH_NANOVDB OFF CACHE BOOL "" FORCE)
set(WITH_QUADRIFLOW OFF CACHE BOOL "" FORCE)
set(WITH_SDL OFF CACHE BOOL "" FORCE)

View File

@ -69,6 +69,7 @@ endif()
if(UNIX AND NOT APPLE)
set(WITH_DOC_MANPAGE ON CACHE BOOL "" FORCE)
set(WITH_GHOST_XDND ON CACHE BOOL "" FORCE)
set(WITH_PULSEAUDIO ON CACHE BOOL "" FORCE)
set(WITH_X11_XINPUT ON CACHE BOOL "" FORCE)
set(WITH_X11_XF86VMODE ON CACHE BOOL "" FORCE)
endif()

View File

@ -16,6 +16,7 @@ set(WITH_PYTHON_INSTALL OFF CACHE BOOL "" FORCE)
# so the python module doesn't hold the audio device and loads quickly.
set(WITH_AUDASPACE OFF CACHE BOOL "" FORCE)
set(WITH_JACK OFF CACHE BOOL "" FORCE)
set(WITH_PULSEAUDIO OFF CACHE BOOL "" FORCE)
set(WITH_SDL OFF CACHE BOOL "" FORCE)
set(WITH_OPENAL OFF CACHE BOOL "" FORCE)
set(WITH_CODEC_FFMPEG OFF CACHE BOOL "" FORCE)

View File

@ -569,6 +569,9 @@ function(SETUP_LIBDIRS)
if(WITH_JACK AND NOT WITH_JACK_DYNLOAD)
link_directories(${JACK_LIBPATH})
endif()
if(WITH_PULSEAUDIO AND NOT WITH_PULSEAUDIO_DYNLOAD)
link_directories(${LIBPULSE_LIBPATH})
endif()
if(WITH_CODEC_SNDFILE)
link_directories(${LIBSNDFILE_LIBPATH})
endif()

View File

@ -552,6 +552,14 @@ if(WITH_JACK)
endif()
endif()
# Pulse is intended to use the system library.
if(WITH_PULSEAUDIO)
find_package_wrapper(Pulse)
if(NOT PULSE_FOUND)
set(WITH_PULSEAUDIO OFF)
endif()
endif()
# Audio IO
if(WITH_SYSTEM_AUDASPACE)
find_package_wrapper(Audaspace)

View File

@ -60,6 +60,9 @@
/** \defgroup audopenal Audaspace OpenAL
* \ingroup audaspace
*/
/** \defgroup audpulseaudio Audaspace PulseAudio
* \ingroup audaspace
*/
/** \defgroup audpython Audaspace Python
* \ingroup audaspace
*/

View File

@ -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})

View File

@ -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})

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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);

View File

@ -302,6 +302,10 @@ if(WITH_JACK)
add_definitions(-DWITH_JACK)
endif()
if(WITH_PULSEAUDIO)
add_definitions(-DWITH_PULSEAUDIO)
endif()
if(WITH_OPENCOLLADA)
add_definitions(-DWITH_COLLADA)
endif()

View File

@ -284,6 +284,10 @@ if(WITH_LIBMV)
add_definitions(-DWITH_LIBMV)
endif()
if(WITH_PULSEAUDIO)
add_definitions(-DWITH_PULSEAUDIO)
endif()
if(WITH_MOD_OCEANSIM)
add_definitions(-DWITH_OCEANSIM)
endif()

View File

@ -50,6 +50,7 @@ static PyStructSequence_Field app_builtopts_info_fields[] = {
{"sdl", NULL},
{"sdl_dynload", NULL},
{"jack", NULL},
{"pulseaudio", NULL},
{"libmv", NULL},
{"mod_oceansim", NULL},
{"mod_remesh", NULL},
@ -217,6 +218,12 @@ static PyObject *make_builtopts_info(void)
SetObjIncref(Py_False);
#endif
#ifdef WITH_PULSEAUDIO
SetObjIncref(Py_True);
#else
SetObjIncref(Py_False);
#endif
#ifdef WITH_LIBMV
SetObjIncref(Py_True);
#else

View File

@ -1330,7 +1330,7 @@ static const char arg_handle_audio_set_doc[] =
"\n\t"
"Force sound system to a specific device."
"\n\t"
"'None' 'SDL' 'OpenAL' 'JACK'.";
"'None' 'SDL' 'OpenAL' 'JACK' 'PulseAudio'.";
static int arg_handle_audio_set(int argc, const char **argv, void *UNUSED(data))
{
if (argc < 1) {