From: Stephen Sinclair Date: Tue, 16 Oct 2018 13:09:53 +0000 (+0200) Subject: Merge remote-tracking branch 'upstream/pr/141' X-Git-Tag: 5.1.0~42 X-Git-Url: https://git.carlh.net/gitweb/?a=commitdiff_plain;h=ab57ca2569c2ffa0b92b1222b38f2797805a629c;hp=ab4af532a13c5659e3355fb72428546a60e1925d;p=rtaudio.git Merge remote-tracking branch 'upstream/pr/141' --- diff --git a/.travis.yml b/.travis.yml index 2c687a7..838f834 100644 --- a/.travis.yml +++ b/.travis.yml @@ -26,12 +26,6 @@ matrix: - os: linux env: HOST="" API="oss" compiler: clang - - os: linux - env: HOST="--host=i686-w64-mingw32" API="winmm" CPPFLAGS="-Wno-unused-function" - compiler: gcc - - os: linux - env: HOST="--host=x86_64-w64-mingw32" API="winmm" CPPFLAGS="-Wno-unused-function" - compiler: gcc - os: linux env: HOST="--host=i686-w64-mingw32" API="dsound" CPPFLAGS="-Wno-unused-function" compiler: gcc diff --git a/CMakeLists.txt b/CMakeLists.txt index d5a00c7..d220bc7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,143 +1,309 @@ -cmake_minimum_required(VERSION 2.8.10) -project(RtAudio) +# Set minimum CMake required version for this project. +cmake_minimum_required(VERSION 3.0 FATAL_ERROR) -include(CTest) -include(CheckFunctionExists) +# Define a C++ project. +project(RtAudio LANGUAGES CXX) + +# Check for Jack (any OS) +find_library(JACK_LIB jack) +find_package(PkgConfig) +pkg_check_modules(jack jack) +if(JACK_LIB OR jack_FOUND) + set(HAVE_JACK TRUE) +endif() + +# Check for Pulse (any OS) +pkg_check_modules(pulse libpulse-simple) + +# Check for known non-Linux unix-likes +if (CMAKE_SYSTEM_NAME MATCHES "kNetBSD.*|NetBSD.*") + message(STATUS "NetBSD detected, using OSS") + set(xBSD ON) +elseif(UNIX AND NOT APPLE) + set(LINUX ON) +endif() + +# Necessary for Windows +if(WIN32) + set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON) + set(CMAKE_DEBUG_POSTFIX "d") +endif() +# Build Options +option(BUILD_SHARED_LIBS "Compile library shared lib." TRUE) +option(BUILD_STATIC_LIBS "Compile library static lib." TRUE) +option(BUILD_TESTING "Compile test programs." TRUE) option(BUILD_PYTHON "Build PyRtAudio python bindings" OFF) -option(AUDIO_WINDOWS_DS "Build Windows DirectSound API" OFF) -option(AUDIO_WINDOWS_ASIO "Build Windows ASIO API" OFF) -option(AUDIO_WINDOWS_WASAPI "Build Windows WASAPI API" OFF) -option(AUDIO_LINUX_OSS "Build Linux OSS API" OFF) -option(AUDIO_LINUX_ALSA "Build Linux ALSA API" OFF) -option(AUDIO_LINUX_PULSE "Build Linux PulseAudio API" OFF) -option(AUDIO_UNIX_JACK "Build Unix JACK audio server API" OFF) -option(AUDIO_OSX_CORE "Build Mac OSX CoreAudio API" OFF) +set(CMAKE_BUILD_TYPE "Release" CACHE STRING "Build type (Release,Debug)") -if (CMAKE_BUILD_TYPE STREQUAL "Debug") - add_definitions(-D__RTAUDIO_DEBUG__) -endif () +# API Options +option(RTAUDIO_API_DS "Build DirectSound API" OFF) +option(RTAUDIO_API_ASIO "Build ASIO API" OFF) +option(RTAUDIO_API_WASAPI "Build WASAPI API" ${WIN32}) +option(RTAUDIO_API_OSS "Build OSS4 API" ${xBSD}) +option(RTAUDIO_API_ALSA "Build ALSA API" ${LINUX}) +option(RTAUDIO_API_PULSE "Build PulseAudio API" ${pulse_FOUND}) +option(RTAUDIO_API_JACK "Build JACK audio server API" ${HAVE_JACK}) +option(RTAUDIO_API_CORE "Build CoreAudio API" ${APPLE}) +# Check for functions +include(CheckFunctionExists) check_function_exists(gettimeofday HAVE_GETTIMEOFDAY) - if (HAVE_GETTIMEOFDAY) add_definitions(-DHAVE_GETTIMEOFDAY) endif () +# Add -Wall if possible if (CMAKE_COMPILER_IS_GNUCXX) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall") endif (CMAKE_COMPILER_IS_GNUCXX) -set(rtaudio_SOURCES RtAudio.cpp rtaudio_c.cpp) +# Add debug flags +if (CMAKE_BUILD_TYPE STREQUAL "Debug") + add_definitions(-D__RTAUDIO_DEBUG__) + if (CMAKE_COMPILER_IS_GNUCXX) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Werror") + endif (CMAKE_COMPILER_IS_GNUCXX) +endif () + +# Read libtool version info from configure.ac +set(R "m4_define\\(\\[lt_([a-z]+)\\], ([0-9]+)\\)") +file(STRINGS "${CMAKE_CURRENT_SOURCE_DIR}/configure.ac" CONFIGAC + REGEX ${R}) +foreach(_S ${CONFIGAC}) + string(REGEX REPLACE ${R} "\\1" k ${_S}) + string(REGEX REPLACE ${R} "\\2" v ${_S}) + set(SO_${k} ${v}) +endforeach() +math(EXPR SO_current_minus_age "${SO_current} - ${SO_age}") +set(SO_VER "${SO_current_minus_age}") +set(FULL_VER "${SO_current_minus_age}.${SO_revision}.${SO_age}") + +# Read package version info from configure.ac +set(R "AC_INIT\\(RtAudio, ([0-9\\.]+),.*\\)") +file(STRINGS "${CMAKE_CURRENT_SOURCE_DIR}/configure.ac" CONFIGAC + REGEX ${R}) +string(REGEX REPLACE ${R} "\\1" PACKAGE_VERSION ${CONFIGAC}) +# Init variables +set(rtaudio_SOURCES RtAudio.cpp RtAudio.h rtaudio_c.cpp rtaudio_c.h) set(LINKLIBS) -if (CMAKE_SYSTEM_NAME MATCHES "kNetBSD.*|NetBSD.*") - message(STATUS "NetBSD detected, using OSS") - find_package(Threads REQUIRED CMAKE_THREAD_PREFER_PTHREAD) - list(APPEND LINKLIBS ossaudio ${CMAKE_THREAD_LIBS_INIT}) - set(AUDIO_LINUX_OSS ON) -elseif (UNIX AND NOT APPLE) - if (NOT AUDIO_LINUX_PULSE AND NOT AUDIO_LINUX_ALSA AND NOT AUDIO_LINUX_OSS AND NOT AUDIO_UNIX_JACK) - set(AUDIO_LINUX_ALSA ON) - endif() - - if (AUDIO_LINUX_PULSE) - find_library(PULSE_LIB pulse) - find_library(PULSESIMPLE_LIB pulse-simple) - find_package(Threads REQUIRED CMAKE_THREAD_PREFER_PTHREAD) - list(APPEND LINKLIBS ${PULSE_LIB} ${PULSESIMPLE_LIB} ${CMAKE_THREAD_LIBS_INIT}) - add_definitions(-D__LINUX_PULSE__) - message(STATUS "Using Linux PulseAudio") - endif (AUDIO_LINUX_PULSE) - if (AUDIO_LINUX_ALSA) - find_package(ALSA) - find_package(Threads REQUIRED CMAKE_THREAD_PREFER_PTHREAD) - if (NOT ALSA_FOUND) - message(FATAL_ERROR "ALSA API requested but no ALSA dev libraries found") - endif() - include_directories(${ALSA_INCLUDE_DIR}) - list(APPEND LINKLIBS ${ALSA_LIBRARY} ${CMAKE_THREAD_LIBS_INIT}) - add_definitions(-D__LINUX_ALSA__) - message(STATUS "Using Linux ALSA") - endif (AUDIO_LINUX_ALSA) -endif () +set(PKGCONFIG_REQUIRES) +set(API_DEFS) +set(API_LIST) + +# Tweak API-specific configuration. + +# Jack +if (RTAUDIO_API_JACK AND jack_FOUND) + set(NEED_PTHREAD ON) + list(APPEND PKGCONFIG_REQUIRES "jack") + list(APPEND API_DEFS "-D__UNIX_JACK__") + list(APPEND API_LIST "jack") + if(jack_FOUND) + list(APPEND LINKLIBS ${jack_LIBRARIES}) + list(APPEND INCDIRS ${jack_INCLUDEDIR}) + else() + list(APPEND LINKLIBS ${JACK_LIB}) + endif() +endif() + +# ALSA +if (RTAUDIO_API_ALSA) + set(NEED_PTHREAD ON) + find_package(ALSA) + if (NOT ALSA_FOUND) + message(FATAL_ERROR "ALSA API requested but no ALSA dev libraries found") + endif() + list(APPEND INCDIRS ${ALSA_INCLUDE_DIR}) + list(APPEND LINKLIBS ${ALSA_LIBRARY}) + list(APPEND PKGCONFIG_REQUIRES "alsa") + list(APPEND API_DEFS "-D__LINUX_ALSA__") + list(APPEND API_LIST "alsa") +endif() + +# OSS +if (RTAUDIO_OSS) + set(NEED_PTHREAD ON) + find_library(OSSAUDIO_LIB ossaudio) + if (OSSAUDIO_LIB) + list(APPEND LINKLIBS ossaudio) + # Note: not an error on some systems + endif() + list(APPEND API_DEFS "-D__LINUX_OSS__") + list(APPEND API_LIST "oss") +endif() + +# Pulse +if (RTAUDIO_API_PULSE) + set(NEED_PTHREAD ON) + find_library(PULSE_LIB pulse) + find_library(PULSESIMPLE_LIB pulse-simple) + list(APPEND LINKLIBS ${PULSE_LIB} ${PULSESIMPLE_LIB}) + list(APPEND PKGCONFIG_REQUIRES "libpulse-simple") + list(APPEND API_DEFS "-D__LINUX_PULSE__") + list(APPEND API_LIST "pulse") +endif() + +# CoreAudio +if (RTAUDIO_API_CORE) + find_library(COREAUDIO_LIB CoreAudio) + find_library(COREFOUNDATION_LIB CoreFoundation) + list(APPEND LINKLIBS ${COREAUDIO_LIB} ${COREFOUNDATION_LIB}) + list(APPEND API_DEFS "-D__MACOSX_CORE__") + list(APPEND API_LIST "core") +endif() -if (APPLE) - if (NOT AUDIO_OSX_CORE AND NOT AUDIO_UNIX_JACK) - set(AUDIO_OSX_CORE ON) - endif() - - if (AUDIO_OSX_CORE) - find_library(COREAUDIO_LIB CoreAudio) - find_library(COREFOUNDATION_LIB CoreFoundation) - list(APPEND LINKLIBS ${COREAUDIO_LIB} ${COREFOUNDATION_LIB}) - add_definitions(-D__MACOSX_CORE__) - message(STATUS "Using OSX CoreAudio") - endif (AUDIO_OSX_CORE) -endif (APPLE) - -# JACK supported on many Unices -if (UNIX) - if (AUDIO_UNIX_JACK) - find_library(JACK_LIB jack) - list(APPEND LINKLIBS ${JACK_LIB}) - add_definitions(-D__UNIX_JACK__) - message(STATUS "Using JACK") - endif (AUDIO_UNIX_JACK) -endif (UNIX) - -if (WIN32) - if (NOT AUDIO_WINDOWS_DS AND NOT AUDIO_WINDOWS_ASIO AND NOT AUDIO_WINDOWS_WASAPI) - set(AUDIO_WINDOWS_WASAPI ON) - endif() - - include_directories(include) - list(APPEND LINKLIBS winmm ole32) - - if (AUDIO_WINDOWS_DS) - add_definitions(-D__WINDOWS_DS__) - message(STATUS "Using Windows DirectSound") - list(APPEND LINKLIBS dsound) - endif (AUDIO_WINDOWS_DS) - if (AUDIO_WINDOWS_WASAPI) - add_definitions(-D__WINDOWS_WASAPI__) - message(STATUS "Using Windows WASAPI") - list(APPEND LINKLIBS uuid ksuser) - endif (AUDIO_WINDOWS_WASAPI) - if (AUDIO_WINDOWS_ASIO) - list(APPEND rtaudio_SOURCES - include/asio.cpp - include/asiodrivers.cpp - include/asiolist.cpp - include/iasiothiscallresolver.cpp) - add_definitions(-D__WINDOWS_ASIO__) - message(STATUS "Using Windows ASIO") - endif (AUDIO_WINDOWS_ASIO) -endif (WIN32) +# ASIO +if (RTAUDIO_API_ASIO) + set(NEED_WIN32LIBS ON) + include_directories(include) + list(APPEND rtaudio_SOURCES + include/asio.cpp + include/asiodrivers.cpp + include/asiolist.cpp + include/iasiothiscallresolver.cpp) + list(APPEND API_DEFS "-D__WINDOWS_ASIO__") + list(APPEND API_LIST "asio") +endif() +# DSound +if (RTAUDIO_API_DS) + set(NEED_WIN32LIBS ON) + list(APPEND LINKLIBS dsound) + list(APPEND API_DEFS "-D__WINDOWS_DS__") + list(APPEND API_LIST "ds") +endif() + +# WASAPI +if (RTAUDIO_API_WASAPI) + set(NEED_WIN32LIBS ON) + list(APPEND LINKLIBS ksuser mfplat mfuuid wmcodecdspuuid) + list(APPEND API_DEFS "-D__WINDOWS_WASAPI__") + list(APPEND API_LIST "wasapi") +endif() + +# Windows libs +if (NEED_WIN32LIBS) + list(APPEND LINKLIBS winmm ole32) +endif() + +# pthread +if (NEED_PTHREAD) + find_package(Threads REQUIRED + CMAKE_THREAD_PREFER_PTHREAD + THREADS_PREFER_PTHREAD_FLAG) + list(APPEND LINKLIBS Threads::Threads) +endif() + +# Create library targets. cmake_policy(SET CMP0042 OLD) -set(PACKAGE_VERSION 5.0.0) -add_library(rtaudio SHARED ${rtaudio_SOURCES}) -add_library(rtaudio_static STATIC ${rtaudio_SOURCES}) +set(LIB_TARGETS) +if(BUILD_SHARED_LIBS) + add_library(rtaudio SHARED ${rtaudio_SOURCES}) + list(APPEND LIB_TARGETS rtaudio) + + # Add headers destination for install rule. + set_target_properties(rtaudio PROPERTIES PUBLIC_HEADER RtAudio.h + SOVERSION ${SO_VER} + VERSION ${FULL_VER}) + + # Set include paths, populate target interface. + target_include_directories(rtaudio PRIVATE + $ + $ + ${INCDIRS}) + + # Set compile-time definitions + target_compile_definitions(rtaudio PRIVATE ${API_DEFS}) + target_compile_definitions(rtaudio PRIVATE RTAUDIO_EXPORT) + + target_link_libraries(rtaudio ${LINKLIBS}) +endif() + +if(BUILD_STATIC_LIBS) + add_library(rtaudio_static STATIC ${rtaudio_SOURCES}) + list(APPEND LIB_TARGETS rtaudio_static) + + # Add headers destination for install rule. + set_target_properties(rtaudio_static PROPERTIES PUBLIC_HEADER RtAudio.h + SOVERSION ${SO_VER} + VERSION ${FULL_VER}) + + # Set include paths, populate target interface. + target_include_directories(rtaudio_static PRIVATE + $ + $ + ${INCDIRS}) -target_link_libraries(rtaudio ${LINKLIBS}) + # Set compile-time definitions + target_compile_definitions(rtaudio_static PRIVATE ${API_DEFS}) + target_link_libraries(rtaudio_static ${LINKLIBS}) +endif() + +# Set standard installation directories. +include(GNUInstallDirs) + +# Subdirs if (BUILD_TESTING) - add_subdirectory(tests) + include(CTest) + add_subdirectory(tests) endif (BUILD_TESTING) +# Message +string(REPLACE ";" " " apilist "${API_LIST}") +message(STATUS "Compiling with support for: ${apilist}") + +# PkgConfig file +string(REPLACE ";" " " req "${PKGCONFIG_REQUIRES}") +string(REPLACE ";" " " api "${API_DEFS}") +set(prefix ${CMAKE_INSTALL_PREFIX}) configure_file("rtaudio.pc.in" "rtaudio.pc" @ONLY) -install(TARGETS rtaudio - LIBRARY DESTINATION lib) +# Add install rule. +install(TARGETS ${LIB_TARGETS} + EXPORT RtAudioTargets + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) -install(TARGETS rtaudio_static - ARCHIVE DESTINATION lib) +# Store the package in the user registry. +export(PACKAGE RtAudio) -install( - FILES RtAudio.h - DESTINATION include) +# Set installation path for CMake files. +if(WIN32) + set(RTAUDIO_CMAKE_DESTINATION cmake) +else() + set(RTAUDIO_CMAKE_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/RtAudio) +endif() + +# Create CMake configuration export file. +file(WRITE ${CMAKE_CURRENT_BINARY_DIR}/RtAudioConfig.cmake "include(\${CMAKE_CURRENT_LIST_DIR}/RtAudioTargets.cmake)") + +# Install CMake configuration export file. +install(FILES ${CMAKE_CURRENT_BINARY_DIR}/RtAudioConfig.cmake + DESTINATION ${RTAUDIO_CMAKE_DESTINATION}) + +# Export library target (build-tree). +export(EXPORT RtAudioTargets + NAMESPACE RtAudio::) + +# Export library target (install-tree). +install(EXPORT RtAudioTargets + DESTINATION ${RTAUDIO_CMAKE_DESTINATION} + NAMESPACE RtAudio::) + +# Configure uninstall target. +configure_file( + "${CMAKE_CURRENT_SOURCE_DIR}/cmake/RtAudioConfigUninstall.cmake.in" + "${CMAKE_CURRENT_BINARY_DIR}/RtAudioConfigUninstall.cmake" @ONLY) + +# Create uninstall target. +add_custom_target(uninstall + COMMAND ${CMAKE_COMMAND} -P ${CMAKE_CURRENT_BINARY_DIR}/RtAudioConfigUninstall.cmake) install( FILES ${CMAKE_CURRENT_BINARY_DIR}/rtaudio.pc - DESTINATION lib/pkgconfig) + DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig) diff --git a/Makefile.am b/Makefile.am index f83007c..2a05035 100644 --- a/Makefile.am +++ b/Makefile.am @@ -6,6 +6,7 @@ endif AM_CXXFLAGS = @visibility@ lib_LTLIBRARIES = %D%/librtaudio.la +%C%_librtaudio_la_CXXFLAGS = -DRTAUDIO_EXPORT %C%_librtaudio_la_LDFLAGS = -no-undefined -export-dynamic -version-info @SO_VERSION@ %C%_librtaudio_la_SOURCES = \ %D%/RtAudio.cpp \ diff --git a/RtAudio.cpp b/RtAudio.cpp index e1462f4..d23524d 100644 --- a/RtAudio.cpp +++ b/RtAudio.cpp @@ -1,4 +1,4 @@ -/************************************************************************/ +/************************************************************************/ /*! \class RtAudio \brief Realtime audio i/o C++ classes. @@ -98,39 +98,95 @@ std::string RtAudio :: getVersion( void ) return RTAUDIO_VERSION; } -void RtAudio :: getCompiledApi( std::vector &apis ) -{ - apis.clear(); +// Define API names and display names. +// Must be in same order as API enum. +extern "C" { +const char* rtaudio_api_names[][2] = { + { "unspecified" , "Unknown" }, + { "alsa" , "ALSA" }, + { "pulse" , "Pulse" }, + { "oss" , "OpenSoundSystem" }, + { "jack" , "Jack" }, + { "core" , "CoreAudio" }, + { "wasapi" , "WASAPI" }, + { "asio" , "ASIO" }, + { "ds" , "DirectSound" }, + { "dummy" , "Dummy" }, +}; +const unsigned int rtaudio_num_api_names = + sizeof(rtaudio_api_names)/sizeof(rtaudio_api_names[0]); - // The order here will control the order of RtAudio's API search in - // the constructor. +// The order here will control the order of RtAudio's API search in +// the constructor. +extern "C" const RtAudio::Api rtaudio_compiled_apis[] = { #if defined(__UNIX_JACK__) - apis.push_back( UNIX_JACK ); + RtAudio::UNIX_JACK, #endif #if defined(__LINUX_PULSE__) - apis.push_back( LINUX_PULSE ); + RtAudio::LINUX_PULSE, #endif #if defined(__LINUX_ALSA__) - apis.push_back( LINUX_ALSA ); + RtAudio::LINUX_ALSA, #endif #if defined(__LINUX_OSS__) - apis.push_back( LINUX_OSS ); + RtAudio::LINUX_OSS, #endif #if defined(__WINDOWS_ASIO__) - apis.push_back( WINDOWS_ASIO ); + RtAudio::WINDOWS_ASIO, #endif #if defined(__WINDOWS_WASAPI__) - apis.push_back( WINDOWS_WASAPI ); + RtAudio::WINDOWS_WASAPI, #endif #if defined(__WINDOWS_DS__) - apis.push_back( WINDOWS_DS ); + RtAudio::WINDOWS_DS, #endif #if defined(__MACOSX_CORE__) - apis.push_back( MACOSX_CORE ); + RtAudio::MACOSX_CORE, #endif #if defined(__RTAUDIO_DUMMY__) - apis.push_back( RTAUDIO_DUMMY ); + RtAudio::RTAUDIO_DUMMY, #endif + RtAudio::UNSPECIFIED, +}; +extern "C" const unsigned int rtaudio_num_compiled_apis = + sizeof(rtaudio_compiled_apis)/sizeof(rtaudio_compiled_apis[0])-1; +} + +// This is a compile-time check that rtaudio_num_api_names == RtAudio::NUM_APIS. +// If the build breaks here, check that they match. +template class StaticAssert { private: StaticAssert() {} }; +template<> class StaticAssert{ public: StaticAssert() {} }; +class StaticAssertions { StaticAssertions() { + StaticAssert(); +}}; + +void RtAudio :: getCompiledApi( std::vector &apis ) +{ + apis = std::vector(rtaudio_compiled_apis, + rtaudio_compiled_apis + rtaudio_num_compiled_apis); +} + +std::string RtAudio :: getApiName( RtAudio::Api api ) +{ + if (api < 0 || api >= RtAudio::NUM_APIS) + return ""; + return rtaudio_api_names[api][0]; +} + +std::string RtAudio :: getApiDisplayName( RtAudio::Api api ) +{ + if (api < 0 || api >= RtAudio::NUM_APIS) + return "Unknown"; + return rtaudio_api_names[api][1]; +} + +RtAudio::Api RtAudio :: getCompiledApiByName( const std::string &name ) +{ + unsigned int i=0; + for (i = 0; i < rtaudio_num_compiled_apis; ++i) + if (name == rtaudio_api_names[rtaudio_compiled_apis[i]][0]) + return rtaudio_compiled_apis[i]; + return RtAudio::UNSPECIFIED; } void RtAudio :: openRtApi( RtAudio::Api api ) @@ -1973,7 +2029,7 @@ unsigned int RtApiJack :: getDeviceCount( void ) const char **ports; std::string port, previousPort; unsigned int nChannels = 0, nDevices = 0; - ports = jack_get_ports( client, NULL, NULL, 0 ); + ports = jack_get_ports( client, NULL, JACK_DEFAULT_AUDIO_TYPE, 0 ); if ( ports ) { // Parse the port names up to the first colon (:). size_t iColon = 0; @@ -2012,7 +2068,7 @@ RtAudio::DeviceInfo RtApiJack :: getDeviceInfo( unsigned int device ) const char **ports; std::string port, previousPort; unsigned int nPorts = 0, nDevices = 0; - ports = jack_get_ports( client, NULL, NULL, 0 ); + ports = jack_get_ports( client, NULL, JACK_DEFAULT_AUDIO_TYPE, 0 ); if ( ports ) { // Parse the port names up to the first colon (:). size_t iColon = 0; @@ -2047,7 +2103,7 @@ RtAudio::DeviceInfo RtApiJack :: getDeviceInfo( unsigned int device ) // Count the available ports containing the client name as device // channels. Jack "input ports" equal RtAudio output channels. unsigned int nChannels = 0; - ports = jack_get_ports( client, info.name.c_str(), NULL, JackPortIsInput ); + ports = jack_get_ports( client, info.name.c_str(), JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput ); if ( ports ) { while ( ports[ nChannels ] ) nChannels++; free( ports ); @@ -2056,7 +2112,7 @@ RtAudio::DeviceInfo RtApiJack :: getDeviceInfo( unsigned int device ) // Jack "output ports" equal RtAudio input channels. nChannels = 0; - ports = jack_get_ports( client, info.name.c_str(), NULL, JackPortIsOutput ); + ports = jack_get_ports( client, info.name.c_str(), JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput ); if ( ports ) { while ( ports[ nChannels ] ) nChannels++; free( ports ); @@ -2168,7 +2224,7 @@ bool RtApiJack :: probeDeviceOpen( unsigned int device, StreamMode mode, unsigne const char **ports; std::string port, previousPort, deviceName; unsigned int nPorts = 0, nDevices = 0; - ports = jack_get_ports( client, NULL, NULL, 0 ); + ports = jack_get_ports( client, NULL, JACK_DEFAULT_AUDIO_TYPE, 0 ); if ( ports ) { // Parse the port names up to the first colon (:). size_t iColon = 0; @@ -2192,22 +2248,24 @@ bool RtApiJack :: probeDeviceOpen( unsigned int device, StreamMode mode, unsigne return FAILURE; } - // Count the available ports containing the client name as device - // channels. Jack "input ports" equal RtAudio output channels. - unsigned int nChannels = 0; unsigned long flag = JackPortIsInput; if ( mode == INPUT ) flag = JackPortIsOutput; - ports = jack_get_ports( client, deviceName.c_str(), NULL, flag ); - if ( ports ) { - while ( ports[ nChannels ] ) nChannels++; - free( ports ); - } - // Compare the jack ports for specified client to the requested number of channels. - if ( nChannels < (channels + firstChannel) ) { - errorStream_ << "RtApiJack::probeDeviceOpen: requested number of channels (" << channels << ") + offset (" << firstChannel << ") not found for specified device (" << device << ":" << deviceName << ")."; - errorText_ = errorStream_.str(); - return FAILURE; + if ( ! (options && (options->flags & RTAUDIO_JACK_DONT_CONNECT)) ) { + // Count the available ports containing the client name as device + // channels. Jack "input ports" equal RtAudio output channels. + unsigned int nChannels = 0; + ports = jack_get_ports( client, deviceName.c_str(), JACK_DEFAULT_AUDIO_TYPE, flag ); + if ( ports ) { + while ( ports[ nChannels ] ) nChannels++; + free( ports ); + } + // Compare the jack ports for specified client to the requested number of channels. + if ( nChannels < (channels + firstChannel) ) { + errorStream_ << "RtApiJack::probeDeviceOpen: requested number of channels (" << channels << ") + offset (" << firstChannel << ") not found for specified device (" << device << ":" << deviceName << ")."; + errorText_ = errorStream_.str(); + return FAILURE; + } } // Check the jack server sample rate. @@ -2221,7 +2279,7 @@ bool RtApiJack :: probeDeviceOpen( unsigned int device, StreamMode mode, unsigne stream_.sampleRate = jackRate; // Get the latency of the JACK port. - ports = jack_get_ports( client, deviceName.c_str(), NULL, flag ); + ports = jack_get_ports( client, deviceName.c_str(), JACK_DEFAULT_AUDIO_TYPE, flag ); if ( ports[ firstChannel ] ) { // Added by Ge Wang jack_latency_callback_mode_t cbmode = (mode == INPUT ? JackCaptureLatency : JackPlaybackLatency); @@ -2453,7 +2511,7 @@ void RtApiJack :: startStream( void ) // Get the list of available ports. if ( shouldAutoconnect_ && (stream_.mode == OUTPUT || stream_.mode == DUPLEX) ) { result = 1; - ports = jack_get_ports( handle->client, handle->deviceName[0].c_str(), NULL, JackPortIsInput); + ports = jack_get_ports( handle->client, handle->deviceName[0].c_str(), JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput); if ( ports == NULL) { errorText_ = "RtApiJack::startStream(): error determining available JACK input ports!"; goto unlock; @@ -2477,7 +2535,7 @@ void RtApiJack :: startStream( void ) if ( shouldAutoconnect_ && (stream_.mode == INPUT || stream_.mode == DUPLEX) ) { result = 1; - ports = jack_get_ports( handle->client, handle->deviceName[1].c_str(), NULL, JackPortIsOutput ); + ports = jack_get_ports( handle->client, handle->deviceName[1].c_str(), JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput ); if ( ports == NULL) { errorText_ = "RtApiJack::startStream(): error determining available JACK output ports!"; goto unlock; @@ -3163,8 +3221,8 @@ bool RtApiAsio :: probeDeviceOpen( unsigned int device, StreamMode mode, unsigne result = ASIOCreateBuffers( handle->bufferInfos, nChannels, stream_.bufferSize, &asioCallbacks ); if ( result != ASE_OK ) { // Standard method failed. This can happen with strict/misbehaving drivers that return valid buffer size ranges - // but only accept the preferred buffer size as parameter for ASIOCreateBuffers. eg. Creatives ASIO driver - // in that case, let's be naïve and try that instead + // but only accept the preferred buffer size as parameter for ASIOCreateBuffers (e.g. Creative's ASIO driver). + // In that case, let's be naïve and try that instead. *bufferSize = preferSize; stream_.bufferSize = *bufferSize; result = ASIOCreateBuffers( handle->bufferInfos, nChannels, stream_.bufferSize, &asioCallbacks ); @@ -3687,11 +3745,32 @@ static const char* getAsioErrorString( ASIOError result ) #ifndef INITGUID #define INITGUID #endif + +#include +#include +#include +#include +#include + #include #include #include #include -#include + +#ifndef MF_E_TRANSFORM_NEED_MORE_INPUT + #define MF_E_TRANSFORM_NEED_MORE_INPUT _HRESULT_TYPEDEF_(0xc00d6d72) +#endif + +#ifndef MFSTARTUP_NOSOCKET + #define MFSTARTUP_NOSOCKET 0x1 +#endif + +#ifdef _MSC_VER + #pragma comment( lib, "ksuser" ) + #pragma comment( lib, "mfplat.lib" ) + #pragma comment( lib, "mfuuid.lib" ) + #pragma comment( lib, "wmcodecdspuuid" ) +#endif //============================================================================= @@ -3865,6 +3944,198 @@ private: //----------------------------------------------------------------------------- +// In order to satisfy WASAPI's buffer requirements, we need a means of converting sample rate +// between HW and the user. The WasapiResampler class is used to perform this conversion between +// HwIn->UserIn and UserOut->HwOut during the stream callback loop. +class WasapiResampler +{ +public: + WasapiResampler( bool isFloat, unsigned int bitsPerSample, unsigned int channelCount, + unsigned int inSampleRate, unsigned int outSampleRate ) + : _bytesPerSample( bitsPerSample / 8 ) + , _channelCount( channelCount ) + , _sampleRatio( ( float ) outSampleRate / inSampleRate ) + , _transformUnk( NULL ) + , _transform( NULL ) + , _mediaType( NULL ) + , _inputMediaType( NULL ) + , _outputMediaType( NULL ) + + #ifdef __IWMResamplerProps_FWD_DEFINED__ + , _resamplerProps( NULL ) + #endif + { + // 1. Initialization + + MFStartup( MF_VERSION, MFSTARTUP_NOSOCKET ); + + // 2. Create Resampler Transform Object + + CoCreateInstance( CLSID_CResamplerMediaObject, NULL, CLSCTX_INPROC_SERVER, + IID_IUnknown, ( void** ) &_transformUnk ); + + _transformUnk->QueryInterface( IID_PPV_ARGS( &_transform ) ); + + #ifdef __IWMResamplerProps_FWD_DEFINED__ + _transformUnk->QueryInterface( IID_PPV_ARGS( &_resamplerProps ) ); + _resamplerProps->SetHalfFilterLength( 60 ); // best conversion quality + #endif + + // 3. Specify input / output format + + MFCreateMediaType( &_mediaType ); + _mediaType->SetGUID( MF_MT_MAJOR_TYPE, MFMediaType_Audio ); + _mediaType->SetGUID( MF_MT_SUBTYPE, isFloat ? MFAudioFormat_Float : MFAudioFormat_PCM ); + _mediaType->SetUINT32( MF_MT_AUDIO_NUM_CHANNELS, channelCount ); + _mediaType->SetUINT32( MF_MT_AUDIO_SAMPLES_PER_SECOND, inSampleRate ); + _mediaType->SetUINT32( MF_MT_AUDIO_BLOCK_ALIGNMENT, _bytesPerSample * channelCount ); + _mediaType->SetUINT32( MF_MT_AUDIO_AVG_BYTES_PER_SECOND, _bytesPerSample * channelCount * inSampleRate ); + _mediaType->SetUINT32( MF_MT_AUDIO_BITS_PER_SAMPLE, bitsPerSample ); + _mediaType->SetUINT32( MF_MT_ALL_SAMPLES_INDEPENDENT, TRUE ); + + MFCreateMediaType( &_inputMediaType ); + _mediaType->CopyAllItems( _inputMediaType ); + + _transform->SetInputType( 0, _inputMediaType, 0 ); + + MFCreateMediaType( &_outputMediaType ); + _mediaType->CopyAllItems( _outputMediaType ); + + _outputMediaType->SetUINT32( MF_MT_AUDIO_SAMPLES_PER_SECOND, outSampleRate ); + _outputMediaType->SetUINT32( MF_MT_AUDIO_AVG_BYTES_PER_SECOND, _bytesPerSample * channelCount * outSampleRate ); + + _transform->SetOutputType( 0, _outputMediaType, 0 ); + + // 4. Send stream start messages to Resampler + + _transform->ProcessMessage( MFT_MESSAGE_COMMAND_FLUSH, 0 ); + _transform->ProcessMessage( MFT_MESSAGE_NOTIFY_BEGIN_STREAMING, 0 ); + _transform->ProcessMessage( MFT_MESSAGE_NOTIFY_START_OF_STREAM, 0 ); + } + + ~WasapiResampler() + { + // 8. Send stream stop messages to Resampler + + _transform->ProcessMessage( MFT_MESSAGE_NOTIFY_END_OF_STREAM, 0 ); + _transform->ProcessMessage( MFT_MESSAGE_NOTIFY_END_STREAMING, 0 ); + + // 9. Cleanup + + MFShutdown(); + + SAFE_RELEASE( _transformUnk ); + SAFE_RELEASE( _transform ); + SAFE_RELEASE( _mediaType ); + SAFE_RELEASE( _inputMediaType ); + SAFE_RELEASE( _outputMediaType ); + + #ifdef __IWMResamplerProps_FWD_DEFINED__ + SAFE_RELEASE( _resamplerProps ); + #endif + } + + void Convert( char* outBuffer, const char* inBuffer, unsigned int inSampleCount, unsigned int& outSampleCount ) + { + unsigned int inputBufferSize = _bytesPerSample * _channelCount * inSampleCount; + if ( _sampleRatio == 1 ) + { + // no sample rate conversion required + memcpy( outBuffer, inBuffer, inputBufferSize ); + outSampleCount = inSampleCount; + return; + } + + unsigned int outputBufferSize = ( unsigned int ) ceilf( inputBufferSize * _sampleRatio ) + ( _bytesPerSample * _channelCount ); + + IMFMediaBuffer* rInBuffer; + IMFSample* rInSample; + BYTE* rInByteBuffer = NULL; + + // 5. Create Sample object from input data + + MFCreateMemoryBuffer( inputBufferSize, &rInBuffer ); + + rInBuffer->Lock( &rInByteBuffer, NULL, NULL ); + memcpy( rInByteBuffer, inBuffer, inputBufferSize ); + rInBuffer->Unlock(); + rInByteBuffer = NULL; + + rInBuffer->SetCurrentLength( inputBufferSize ); + + MFCreateSample( &rInSample ); + rInSample->AddBuffer( rInBuffer ); + + // 6. Pass input data to Resampler + + _transform->ProcessInput( 0, rInSample, 0 ); + + SAFE_RELEASE( rInBuffer ); + SAFE_RELEASE( rInSample ); + + // 7. Perform sample rate conversion + + IMFMediaBuffer* rOutBuffer = NULL; + BYTE* rOutByteBuffer = NULL; + + MFT_OUTPUT_DATA_BUFFER rOutDataBuffer; + DWORD rStatus; + DWORD rBytes = outputBufferSize; // maximum bytes accepted per ProcessOutput + + // 7.1 Create Sample object for output data + + memset( &rOutDataBuffer, 0, sizeof rOutDataBuffer ); + MFCreateSample( &( rOutDataBuffer.pSample ) ); + MFCreateMemoryBuffer( rBytes, &rOutBuffer ); + rOutDataBuffer.pSample->AddBuffer( rOutBuffer ); + rOutDataBuffer.dwStreamID = 0; + rOutDataBuffer.dwStatus = 0; + rOutDataBuffer.pEvents = NULL; + + // 7.2 Get output data from Resampler + + if ( _transform->ProcessOutput( 0, 1, &rOutDataBuffer, &rStatus ) == MF_E_TRANSFORM_NEED_MORE_INPUT ) + { + outSampleCount = 0; + SAFE_RELEASE( rOutBuffer ); + SAFE_RELEASE( rOutDataBuffer.pSample ); + return; + } + + // 7.3 Write output data to outBuffer + + SAFE_RELEASE( rOutBuffer ); + rOutDataBuffer.pSample->ConvertToContiguousBuffer( &rOutBuffer ); + rOutBuffer->GetCurrentLength( &rBytes ); + + rOutBuffer->Lock( &rOutByteBuffer, NULL, NULL ); + memcpy( outBuffer, rOutByteBuffer, rBytes ); + rOutBuffer->Unlock(); + rOutByteBuffer = NULL; + + outSampleCount = rBytes / _bytesPerSample / _channelCount; + SAFE_RELEASE( rOutBuffer ); + SAFE_RELEASE( rOutDataBuffer.pSample ); + } + +private: + unsigned int _bytesPerSample; + unsigned int _channelCount; + float _sampleRatio; + + IUnknown* _transformUnk; + IMFTransform* _transform; + IMFMediaType* _mediaType; + IMFMediaType* _inputMediaType; + IMFMediaType* _outputMediaType; + + #ifdef __IWMResamplerProps_FWD_DEFINED__ + IWMResamplerProps* _resamplerProps; + #endif +}; + +//----------------------------------------------------------------------------- + // A structure to hold various information related to the WASAPI implementation. struct WasapiHandle { @@ -3899,10 +4170,9 @@ RtApiWasapi::RtApiWasapi() CLSCTX_ALL, __uuidof( IMMDeviceEnumerator ), ( void** ) &deviceEnumerator_ ); - if ( FAILED( hr ) ) { - errorText_ = "RtApiWasapi::RtApiWasapi: Unable to instantiate device enumerator"; - error( RtAudioError::DRIVER_ERROR ); - } + // If this runs on an old Windows, it will fail. Ignore and proceed. + if ( FAILED( hr ) ) + deviceEnumerator_ = NULL; } //----------------------------------------------------------------------------- @@ -3929,6 +4199,9 @@ unsigned int RtApiWasapi::getDeviceCount( void ) IMMDeviceCollection* captureDevices = NULL; IMMDeviceCollection* renderDevices = NULL; + if ( !deviceEnumerator_ ) + return 0; + // Count capture devices errorText_.clear(); HRESULT hr = deviceEnumerator_->EnumAudioEndpoints( eCapture, DEVICE_STATE_ACTIVE, &captureDevices ); @@ -4130,11 +4403,14 @@ RtAudio::DeviceInfo RtApiWasapi::getDeviceInfo( unsigned int device ) info.duplexChannels = 0; } - // sample rates (WASAPI only supports the one native sample rate) - info.preferredSampleRate = deviceFormat->nSamplesPerSec; - + // sample rates info.sampleRates.clear(); - info.sampleRates.push_back( deviceFormat->nSamplesPerSec ); + + // allow support for all sample rates as we have a built-in sample rate converter + for ( unsigned int i = 0; i < MAX_SAMPLE_RATES; i++ ) { + info.sampleRates.push_back( SAMPLE_RATES[i] ); + } + info.preferredSampleRate = deviceFormat->nSamplesPerSec; // native format info.nativeFormats = 0; @@ -4411,7 +4687,6 @@ bool RtApiWasapi::probeDeviceOpen( unsigned int device, StreamMode mode, unsigne WAVEFORMATEX* deviceFormat = NULL; unsigned int bufferBytes; stream_.state = STREAM_STOPPED; - RtAudio::DeviceInfo deviceInfo; // create API Handle if not already created if ( !stream_.apiHandle ) @@ -4452,20 +4727,6 @@ bool RtApiWasapi::probeDeviceOpen( unsigned int device, StreamMode mode, unsigne goto Exit; } - deviceInfo = getDeviceInfo( device ); - - // validate sample rate - if ( sampleRate != deviceInfo.preferredSampleRate ) - { - errorType = RtAudioError::INVALID_USE; - std::stringstream ss; - ss << "RtApiWasapi::probeDeviceOpen: " << sampleRate - << "Hz sample rate not supported. This device only supports " - << deviceInfo.preferredSampleRate << "Hz."; - errorText_ = ss.str(); - goto Exit; - } - // determine whether index falls within capture or render devices if ( device >= renderDeviceCount ) { if ( mode != INPUT ) { @@ -4549,7 +4810,7 @@ bool RtApiWasapi::probeDeviceOpen( unsigned int device, StreamMode mode, unsigne stream_.nUserChannels[mode] = channels; stream_.channelOffset[mode] = firstChannel; stream_.userFormat = format; - stream_.deviceFormat[mode] = deviceInfo.nativeFormats; + stream_.deviceFormat[mode] = getDeviceInfo( device ).nativeFormats; if ( options && options->flags & RTAUDIO_NONINTERLEAVED ) stream_.userInterleaved = false; @@ -4560,7 +4821,8 @@ bool RtApiWasapi::probeDeviceOpen( unsigned int device, StreamMode mode, unsigne // Set flags for buffer conversion. stream_.doConvertBuffer[mode] = false; if ( stream_.userFormat != stream_.deviceFormat[mode] || - stream_.nUserChannels != stream_.nDeviceChannels ) + stream_.nUserChannels[0] != stream_.nDeviceChannels[0] || + stream_.nUserChannels[1] != stream_.nDeviceChannels[1] ) stream_.doConvertBuffer[mode] = true; else if ( stream_.userInterleaved != stream_.deviceInterleaved[mode] && stream_.nUserChannels[mode] > 1 ) @@ -4649,8 +4911,12 @@ void RtApiWasapi::wasapiThread() WAVEFORMATEX* captureFormat = NULL; WAVEFORMATEX* renderFormat = NULL; + float captureSrRatio = 0.0f; + float renderSrRatio = 0.0f; WasapiBuffer captureBuffer; WasapiBuffer renderBuffer; + WasapiResampler* captureResampler = NULL; + WasapiResampler* renderResampler = NULL; // declare local stream variables RtAudioCallback callback = ( RtAudioCallback ) stream_.callbackInfo.callback; @@ -4658,11 +4924,15 @@ void RtApiWasapi::wasapiThread() unsigned long captureFlags = 0; unsigned int bufferFrameCount = 0; unsigned int numFramesPadding = 0; - bool callbackPushed = false; + unsigned int convBufferSize = 0; + bool callbackPushed = true; bool callbackPulled = false; bool callbackStopped = false; int callbackResult = 0; + // convBuffer is used to store converted buffers between WASAPI and the user + char* convBuffer = NULL; + unsigned int convBuffSize = 0; unsigned int deviceBuffSize = 0; errorText_.clear(); @@ -4685,8 +4955,16 @@ void RtApiWasapi::wasapiThread() goto Exit; } + // init captureResampler + captureResampler = new WasapiResampler( stream_.deviceFormat[INPUT] == RTAUDIO_FLOAT32 || stream_.deviceFormat[INPUT] == RTAUDIO_FLOAT64, + formatBytes( stream_.deviceFormat[INPUT] ) * 8, stream_.nDeviceChannels[INPUT], + captureFormat->nSamplesPerSec, stream_.sampleRate ); + + captureSrRatio = ( ( float ) captureFormat->nSamplesPerSec / stream_.sampleRate ); + // initialize capture stream according to desire buffer size - REFERENCE_TIME desiredBufferPeriod = ( REFERENCE_TIME ) ( ( float ) stream_.bufferSize * 10000000 / captureFormat->nSamplesPerSec ); + float desiredBufferSize = stream_.bufferSize * captureSrRatio; + REFERENCE_TIME desiredBufferPeriod = ( REFERENCE_TIME ) ( ( float ) desiredBufferSize * 10000000 / captureFormat->nSamplesPerSec ); if ( !captureClient ) { hr = captureAudioClient->Initialize( AUDCLNT_SHAREMODE_SHARED, @@ -4733,7 +5011,7 @@ void RtApiWasapi::wasapiThread() } // scale outBufferSize according to stream->user sample rate ratio - unsigned int outBufferSize = ( unsigned int ) stream_.bufferSize * stream_.nDeviceChannels[INPUT]; + unsigned int outBufferSize = ( unsigned int ) ceilf( stream_.bufferSize * captureSrRatio ) * stream_.nDeviceChannels[INPUT]; inBufferSize *= stream_.nDeviceChannels[INPUT]; // set captureBuffer size @@ -4762,8 +5040,16 @@ void RtApiWasapi::wasapiThread() goto Exit; } + // init renderResampler + renderResampler = new WasapiResampler( stream_.deviceFormat[OUTPUT] == RTAUDIO_FLOAT32 || stream_.deviceFormat[OUTPUT] == RTAUDIO_FLOAT64, + formatBytes( stream_.deviceFormat[OUTPUT] ) * 8, stream_.nDeviceChannels[OUTPUT], + stream_.sampleRate, renderFormat->nSamplesPerSec ); + + renderSrRatio = ( ( float ) renderFormat->nSamplesPerSec / stream_.sampleRate ); + // initialize render stream according to desire buffer size - REFERENCE_TIME desiredBufferPeriod = ( REFERENCE_TIME ) ( ( float ) stream_.bufferSize * 10000000 / renderFormat->nSamplesPerSec ); + float desiredBufferSize = stream_.bufferSize * renderSrRatio; + REFERENCE_TIME desiredBufferPeriod = ( REFERENCE_TIME ) ( ( float ) desiredBufferSize * 10000000 / renderFormat->nSamplesPerSec ); if ( !renderClient ) { hr = renderAudioClient->Initialize( AUDCLNT_SHAREMODE_SHARED, @@ -4810,7 +5096,7 @@ void RtApiWasapi::wasapiThread() } // scale inBufferSize according to user->stream sample rate ratio - unsigned int inBufferSize = ( unsigned int ) stream_.bufferSize * stream_.nDeviceChannels[OUTPUT]; + unsigned int inBufferSize = ( unsigned int ) ceilf( stream_.bufferSize * renderSrRatio ) * stream_.nDeviceChannels[OUTPUT]; outBufferSize *= stream_.nDeviceChannels[OUTPUT]; // set renderBuffer size @@ -4831,20 +5117,30 @@ void RtApiWasapi::wasapiThread() } } - if ( stream_.mode == INPUT ) { - using namespace std; // for roundf + // malloc buffer memory + if ( stream_.mode == INPUT ) + { + using namespace std; // for ceilf + convBuffSize = ( size_t ) ( ceilf( stream_.bufferSize * captureSrRatio ) ) * stream_.nDeviceChannels[INPUT] * formatBytes( stream_.deviceFormat[INPUT] ); deviceBuffSize = stream_.bufferSize * stream_.nDeviceChannels[INPUT] * formatBytes( stream_.deviceFormat[INPUT] ); } - else if ( stream_.mode == OUTPUT ) { + else if ( stream_.mode == OUTPUT ) + { + convBuffSize = ( size_t ) ( ceilf( stream_.bufferSize * renderSrRatio ) ) * stream_.nDeviceChannels[OUTPUT] * formatBytes( stream_.deviceFormat[OUTPUT] ); deviceBuffSize = stream_.bufferSize * stream_.nDeviceChannels[OUTPUT] * formatBytes( stream_.deviceFormat[OUTPUT] ); } - else if ( stream_.mode == DUPLEX ) { + else if ( stream_.mode == DUPLEX ) + { + convBuffSize = std::max( ( size_t ) ( ceilf( stream_.bufferSize * captureSrRatio ) ) * stream_.nDeviceChannels[INPUT] * formatBytes( stream_.deviceFormat[INPUT] ), + ( size_t ) ( ceilf( stream_.bufferSize * renderSrRatio ) ) * stream_.nDeviceChannels[OUTPUT] * formatBytes( stream_.deviceFormat[OUTPUT] ) ); deviceBuffSize = std::max( stream_.bufferSize * stream_.nDeviceChannels[INPUT] * formatBytes( stream_.deviceFormat[INPUT] ), stream_.bufferSize * stream_.nDeviceChannels[OUTPUT] * formatBytes( stream_.deviceFormat[OUTPUT] ) ); } + convBuffSize *= 2; // allow overflow for *SrRatio remainders + convBuffer = ( char* ) malloc( convBuffSize ); stream_.deviceBuffer = ( char* ) malloc( deviceBuffSize ); - if ( !stream_.deviceBuffer ) { + if ( !convBuffer || !stream_.deviceBuffer ) { errorType = RtAudioError::MEMORY_ERROR; errorText_ = "RtApiWasapi::wasapiThread: Error allocating device buffer memory."; goto Exit; @@ -4856,15 +5152,46 @@ void RtApiWasapi::wasapiThread() // Callback Input // ============== // 1. Pull callback buffer from inputBuffer - // 2. If 1. was successful: Convert callback buffer to user format + // 2. If 1. was successful: Convert callback buffer to user sample rate and channel count + // Convert callback buffer to user format - if ( captureAudioClient ) { - // Pull callback buffer from inputBuffer - callbackPulled = captureBuffer.pullBuffer( stream_.deviceBuffer, - ( unsigned int ) stream_.bufferSize * stream_.nDeviceChannels[INPUT], - stream_.deviceFormat[INPUT] ); + if ( captureAudioClient ) + { + int samplesToPull = ( unsigned int ) floorf( stream_.bufferSize * captureSrRatio ); + if ( captureSrRatio != 1 ) + { + // account for remainders + samplesToPull--; + } + + convBufferSize = 0; + while ( convBufferSize < stream_.bufferSize ) + { + // Pull callback buffer from inputBuffer + callbackPulled = captureBuffer.pullBuffer( convBuffer, + samplesToPull * stream_.nDeviceChannels[INPUT], + stream_.deviceFormat[INPUT] ); + + if ( !callbackPulled ) + { + break; + } + + // Convert callback buffer to user sample rate + unsigned int deviceBufferOffset = convBufferSize * stream_.nDeviceChannels[INPUT] * formatBytes( stream_.deviceFormat[INPUT] ); + unsigned int convSamples = 0; + + captureResampler->Convert( stream_.deviceBuffer + deviceBufferOffset, + convBuffer, + samplesToPull, + convSamples ); + + convBufferSize += convSamples; + samplesToPull = 1; // now pull one sample at a time until we have stream_.bufferSize samples + } - if ( callbackPulled ) { + if ( callbackPulled ) + { if ( stream_.doConvertBuffer[INPUT] ) { // Convert callback buffer to user format convertBuffer( stream_.userBuffer[INPUT], @@ -4938,20 +5265,33 @@ void RtApiWasapi::wasapiThread() // Callback Output // =============== // 1. Convert callback buffer to stream format - // 2. Push callback buffer into outputBuffer + // 2. Convert callback buffer to stream sample rate and channel count + // 3. Push callback buffer into outputBuffer - if ( renderAudioClient && callbackPulled ) { - if ( stream_.doConvertBuffer[OUTPUT] ) { - // Convert callback buffer to stream format - convertBuffer( stream_.deviceBuffer, - stream_.userBuffer[OUTPUT], - stream_.convertInfo[OUTPUT] ); + if ( renderAudioClient && callbackPulled ) + { + // if the last call to renderBuffer.PushBuffer() was successful + if ( callbackPushed || convBufferSize == 0 ) + { + if ( stream_.doConvertBuffer[OUTPUT] ) + { + // Convert callback buffer to stream format + convertBuffer( stream_.deviceBuffer, + stream_.userBuffer[OUTPUT], + stream_.convertInfo[OUTPUT] ); + } + + // Convert callback buffer to stream sample rate + renderResampler->Convert( convBuffer, + stream_.deviceBuffer, + stream_.bufferSize, + convBufferSize ); } // Push callback buffer into outputBuffer - callbackPushed = renderBuffer.pushBuffer( stream_.deviceBuffer, - stream_.bufferSize * stream_.nDeviceChannels[OUTPUT], + callbackPushed = renderBuffer.pushBuffer( convBuffer, + convBufferSize * stream_.nDeviceChannels[OUTPUT], stream_.deviceFormat[OUTPUT] ); } else { @@ -5085,7 +5425,10 @@ void RtApiWasapi::wasapiThread() // if the callback buffer was pushed renderBuffer reset callbackPulled flag if ( callbackPushed ) { + // unsetting the callbackPulled flag lets the stream know that + // the audio device is ready for another callback output buffer. callbackPulled = false; + // tick stream time RtApi::tickStreamTime(); } @@ -5097,15 +5440,17 @@ Exit: CoTaskMemFree( captureFormat ); CoTaskMemFree( renderFormat ); + free ( convBuffer ); + delete renderResampler; + delete captureResampler; + CoUninitialize(); + if ( !errorText_.empty() ) + error( errorType ); + // update stream state stream_.state = STREAM_STOPPED; - - if ( errorText_.empty() ) - return; - else - error( errorType ); } //******************** End of __WINDOWS_WASAPI__ *********************// @@ -5121,6 +5466,8 @@ Exit: // Various revisions for RtAudio 4.0 by Gary Scavone, April 2007 // Changed device query structure for RtAudio 4.0.7, January 2010 +#include +#include #include #include #include @@ -7562,7 +7909,7 @@ bool RtApiAlsa :: probeDeviceOpen( unsigned int device, StreamMode mode, unsigne pthread_attr_t attr; pthread_attr_init( &attr ); pthread_attr_setdetachstate( &attr, PTHREAD_CREATE_JOINABLE ); -#ifdef SCHED_RR // Undefined with some OSes (eg: NetBSD 1.6.x with GNU Pthread) +#ifdef SCHED_RR // Undefined with some OSes (e.g. NetBSD 1.6.x with GNU Pthread) if ( options && options->flags & RTAUDIO_SCHEDULE_REALTIME ) { stream_.callbackInfo.doRealtime = true; struct sched_param param; @@ -8011,7 +8358,7 @@ static void *alsaCallbackHandler( void *ptr ) RtApiAlsa *object = (RtApiAlsa *) info->object; bool *isRunning = &info->isRunning; -#ifdef SCHED_RR // Undefined with some OSes (eg: NetBSD 1.6.x with GNU Pthread) +#ifdef SCHED_RR // Undefined with some OSes (e.g. NetBSD 1.6.x with GNU Pthread) if ( info->doRealtime ) { std::cerr << "RtAudio alsa: " << (sched_getscheduler(0) == SCHED_RR ? "" : "_NOT_ ") << @@ -8099,7 +8446,7 @@ static void *pulseaudio_callback( void * user ) RtApiPulse *context = static_cast( cbi->object ); volatile bool *isRunning = &cbi->isRunning; -#ifdef SCHED_RR // Undefined with some OSes (eg: NetBSD 1.6.x with GNU Pthread) +#ifdef SCHED_RR // Undefined with some OSes (e.g. NetBSD 1.6.x with GNU Pthread) if (cbi->doRealtime) { std::cerr << "RtAudio pulse: " << (sched_getscheduler(0) == SCHED_RR ? "" : "_NOT_ ") << @@ -8502,7 +8849,7 @@ bool RtApiPulse::probeDeviceOpen( unsigned int device, StreamMode mode, pthread_attr_t attr; pthread_attr_init( &attr ); pthread_attr_setdetachstate( &attr, PTHREAD_CREATE_JOINABLE ); -#ifdef SCHED_RR // Undefined with some OSes (eg: NetBSD 1.6.x with GNU Pthread) +#ifdef SCHED_RR // Undefined with some OSes (e.g. NetBSD 1.6.x with GNU Pthread) if ( options && options->flags & RTAUDIO_SCHEDULE_REALTIME ) { stream_.callbackInfo.doRealtime = true; struct sched_param param; @@ -9123,7 +9470,7 @@ bool RtApiOss :: probeDeviceOpen( unsigned int device, StreamMode mode, unsigned pthread_attr_t attr; pthread_attr_init( &attr ); pthread_attr_setdetachstate( &attr, PTHREAD_CREATE_JOINABLE ); -#ifdef SCHED_RR // Undefined with some OSes (eg: NetBSD 1.6.x with GNU Pthread) +#ifdef SCHED_RR // Undefined with some OSes (e.g. NetBSD 1.6.x with GNU Pthread) if ( options && options->flags & RTAUDIO_SCHEDULE_REALTIME ) { stream_.callbackInfo.doRealtime = true; struct sched_param param; @@ -9514,7 +9861,7 @@ static void *ossCallbackHandler( void *ptr ) RtApiOss *object = (RtApiOss *) info->object; bool *isRunning = &info->isRunning; -#ifdef SCHED_RR // Undefined with some OSes (eg: NetBSD 1.6.x with GNU Pthread) +#ifdef SCHED_RR // Undefined with some OSes (e.g. NetBSD 1.6.x with GNU Pthread) if (info->doRealtime) { std::cerr << "RtAudio oss: " << (sched_getscheduler(0) == SCHED_RR ? "" : "_NOT_ ") << diff --git a/RtAudio.h b/RtAudio.h index 34a2534..25c2076 100644 --- a/RtAudio.h +++ b/RtAudio.h @@ -48,7 +48,11 @@ #define RTAUDIO_VERSION "5.0.0" #if defined _WIN32 || defined __CYGWIN__ - #define RTAUDIO_DLL_PUBLIC + #if defined(RTAUDIO_EXPORT) + #define RTAUDIO_DLL_PUBLIC __declspec(dllexport) + #else + #define RTAUDIO_DLL_PUBLIC + #endif #else #if __GNUC__ >= 4 #define RTAUDIO_DLL_PUBLIC __attribute__( (visibility( "default" )) ) @@ -285,7 +289,8 @@ class RTAUDIO_DLL_PUBLIC RtAudio WINDOWS_WASAPI, /*!< The Microsoft WASAPI API. */ WINDOWS_ASIO, /*!< The Steinberg Audio Stream I/O API. */ WINDOWS_DS, /*!< The Microsoft Direct Sound API. */ - RTAUDIO_DUMMY /*!< A compilable but non-functional API. */ + RTAUDIO_DUMMY, /*!< A compilable but non-functional API. */ + NUM_APIS /*!< Number of values in this enum. */ }; //! The public device information structure for returning queried values. @@ -298,7 +303,7 @@ class RTAUDIO_DLL_PUBLIC RtAudio bool isDefaultOutput; /*!< true if this is the default output device. */ bool isDefaultInput; /*!< true if this is the default input device. */ std::vector sampleRates; /*!< Supported sample rates (queried from list of standard rates). */ - unsigned int preferredSampleRate; /*!< Preferred sample rate, eg. for WASAPI the system sample rate. */ + unsigned int preferredSampleRate; /*!< Preferred sample rate, e.g. for WASAPI the system sample rate. */ RtAudioFormat nativeFormats; /*!< Bit mask of supported data formats. */ // Default constructor. @@ -397,6 +402,29 @@ class RTAUDIO_DLL_PUBLIC RtAudio */ static void getCompiledApi( std::vector &apis ); + //! Return the name of a specified compiled audio API. + /*! + This obtains a short lower-case name used for identification purposes. + This value is guaranteed to remain identical across library versions. + If the API is unknown, this function will return the empty string. + */ + static std::string getApiName( RtAudio::Api api ); + + //! Return the display name of a specified compiled audio API. + /*! + This obtains a long name used for display purposes. + If the API is unknown, this function will return the empty string. + */ + static std::string getApiDisplayName( RtAudio::Api api ); + + //! Return the compiled audio API having the given name. + /*! + A case insensitive comparison will check the specified name + against the list of compiled APIs, and return the one which + matches. On failure, the function returns UNSPECIFIED. + */ + static RtAudio::Api getCompiledApiByName( const std::string &name ); + //! The class constructor. /*! The constructor performs minor initialization tasks. An exception @@ -1013,7 +1041,7 @@ class RtApiWasapi : public RtApi { public: RtApiWasapi(); - ~RtApiWasapi(); + virtual ~RtApiWasapi(); RtAudio::Api getCurrentApi( void ) { return RtAudio::WINDOWS_WASAPI; } unsigned int getDeviceCount( void ); diff --git a/cmake/RtAudioConfigUninstall.cmake.in b/cmake/RtAudioConfigUninstall.cmake.in new file mode 100644 index 0000000..db894b3 --- /dev/null +++ b/cmake/RtAudioConfigUninstall.cmake.in @@ -0,0 +1,21 @@ +if(NOT EXISTS "@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt") + message(FATAL_ERROR "Cannot find install manifest: \"@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt\"") +endif(NOT EXISTS "@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt") + +file(READ "@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt" files) +string(REGEX REPLACE "\n" ";" files "${files}") +foreach(file ${files}) + message(STATUS "Uninstalling \"$ENV{DESTDIR}${file}\"") + if(EXISTS "$ENV{DESTDIR}${file}") + exec_program( + "@CMAKE_COMMAND@" ARGS "-E remove \"$ENV{DESTDIR}${file}\"" + OUTPUT_VARIABLE rm_out + RETURN_VALUE rm_retval + ) + if(NOT "${rm_retval}" STREQUAL 0) + message(FATAL_ERROR "Problem when removing \"$ENV{DESTDIR}${file}\"") + endif(NOT "${rm_retval}" STREQUAL 0) + else(EXISTS "$ENV{DESTDIR}${file}") + message(STATUS "File \"$ENV{DESTDIR}${file}\" does not exist.") + endif(EXISTS "$ENV{DESTDIR}${file}") +endforeach(file) diff --git a/configure.ac b/configure.ac index 286b7d9..266ff48 100644 --- a/configure.ac +++ b/configure.ac @@ -38,9 +38,9 @@ use_asio="" # configure flags AC_ARG_ENABLE(debug, [AS_HELP_STRING([--enable-debug],[enable various debug output])]) -AC_ARG_WITH(jack, [AS_HELP_STRING([--with-jack], [choose JACK server support (mac and linux only)])]) +AC_ARG_WITH(jack, [AS_HELP_STRING([--with-jack], [choose JACK server support])]) AC_ARG_WITH(alsa, [AS_HELP_STRING([--with-alsa], [choose native ALSA API support (linux only)])]) -AC_ARG_WITH(pulse, [AS_HELP_STRING([--with-pulse], [choose PulseAudio API support (linux only)])]) +AC_ARG_WITH(pulse, [AS_HELP_STRING([--with-pulse], [choose PulseAudio API support (unixes)])]) AC_ARG_WITH(oss, [AS_HELP_STRING([--with-oss], [choose OSS API support (unixes)])]) AC_ARG_WITH(core, [AS_HELP_STRING([--with-core], [choose CoreAudio API support (mac only)])]) AC_ARG_WITH(asio, [AS_HELP_STRING([--with-asio], [choose ASIO API support (win32 only)])]) @@ -86,8 +86,8 @@ AC_CHECK_HEADERS(sys/ioctl.h unistd.h) AS_IF([test "x${GXX}" = "xyes" ], [ CXXFLAGS="-Wall -Wextra ${CXXFLAGS}" AS_IF([ test "x${enable_debug}" = "xyes" ], [ - # Add -Werror in debug mode - CXXFLAGS="-Werror ${CXXFLAGS}" + # Add -Werror in debug mode (except unused-function) + CXXFLAGS="-Werror -Wno-error=unused-function ${CXXFLAGS}" ], [ # hide private symbols in non-debug mode visibility="-fvisibility=hidden" @@ -147,114 +147,150 @@ AC_CONFIG_LINKS( [doc/images/ccrma.gif:doc/images/ccrma.gif] ) # Checks for package options and external software AC_CANONICAL_HOST -AC_MSG_CHECKING([for audio API]) +# Aggregate options into a single string. +AS_IF([test "x$with_jack" = "xyes"], [systems="$systems jack"]) +AS_IF([test "x$with_alsa" = "xyes"], [systems="$systems alsa"]) +AS_IF([test "x$with_pulse" = "xyes"], [systems="$systems pulse"]) +AS_IF([test "x$with_oss" = "xyes"], [systems="$systems oss"]) +AS_IF([test "x$with_core" = "xyes"], [systems="$systems core"]) +AS_IF([test "x$with_asio" = "xyes"], [systems="$systems asio"]) +AS_IF([test "x$with_dsound" = "xyes"], [systems="$systems ds"]) +AS_IF([test "x$with_wasapi" = "xyes"], [systems="$systems wasapi"]) +required=" $systems " + +# If none, assign defaults if any are known for this OS. +# User must specified with-* options for any unknown OS. +AS_IF([test "x$systems" = "x"], + AS_CASE([$host], + [*-*-netbsd*], [systems="oss"], + [*-*-freebsd*], [systems="oss"], + [*-*-linux*], [systems="alsa pulse jack oss"], + [*-apple*], [systems="core jack"], + [*-mingw32*], [systems="asio ds wasapi jack"] + )) + +# If any were specifically requested disabled, do it. +AS_IF([test "x$with_jack" = "xno"], [systems=`echo $systems|tr ' ' \\\\n|grep -v jack`]) +AS_IF([test "x$with_alsa" = "xno"], [systems=`echo $systems|tr ' ' \\\\n|grep -v alsa`]) +AS_IF([test "x$with_pulse" = "xno"], [systems=`echo $systems|tr ' ' \\\\n|grep -v pulse`]) +AS_IF([test "x$with_oss" = "xno"], [systems=`echo $systems|tr ' ' \\\\n|grep -v oss`]) +AS_IF([test "x$with_core" = "xno"], [systems=`echo $systems|tr ' ' \\\\n|grep -v core`]) +AS_IF([test "x$with_asio" = "xno"], [systems=`echo $systems|tr ' ' \\\\n|grep -v asio`]) +AS_IF([test "x$with_dsound" = "xno"], [systems=`echo $systems|tr ' ' \\\\n|grep -v dsound`]) +AS_IF([test "x$with_wasapi" = "xno"], [systems=`echo $systems|tr ' ' \\\\n|grep -v wasapi`]) +systems=" `echo $systems|tr \\\\n ' '` " -AS_IF([test "x$with_jack" = "xyes"], [ - AC_MSG_RESULT([using JACK]) - AC_CHECK_LIB(jack, jack_client_open, , AC_MSG_ERROR([JACK support requires the jack library!])) - api="$api -D__UNIX_JACK__" - req="$req jack" +# For each audio system, check if it is selected and found. +# Note: Order specified above is not necessarily respected. However, +# *actual* priority is set at run-time, see RtAudio::openRtApi. +# One AS_CASE per system, since they are not mutually-exclusive. + +AS_CASE(["$systems"], [*" alsa "*], [ + AC_CHECK_LIB(asound, snd_pcm_open, + [api="$api -D__LINUX_ALSA__" + req="$req alsa" + need_pthread=yes + found="$found ALSA" + LIBS="-lasound $LIBS"], + AS_CASE(["$required"], [*" alsa "*], + AC_MSG_ERROR([ALSA support requires the asound library!]))) ]) +AS_CASE(["$systems"], [*" pulse "*], [ + AC_CHECK_LIB(pulse-simple, pa_simple_flush, + [api="$api -D__LINUX_PULSE__" + req="$req libpulse-simple" + need_pthread=yes + found="$found PulseAudio" + LIBS="-lpulse-simple $LIBS"], + AS_CASE(["$required"], [*" pulse "*], + AC_MSG_ERROR([PulseAudio support requires the pulse-simple library!]))) +]) -AS_CASE([$host], - [*-*-netbsd*], - AS_IF([test "x$api" = "x"], [ - AC_MSG_RESULT([using OSS]) - api="$api -D__LINUX_OSS__" - AC_CHECK_LIB(ossaudio, main, , AC_MSG_ERROR([RtAudio requires the ossaudio library])) - AC_CHECK_LIB(pthread, pthread_create, , AC_MSG_ERROR([RtAudio requires the pthread library!])) - ]), - [*-*-freebsd*], - AS_IF([test "x$api" = "x"], [ - AC_MSG_RESULT([using OSS]) - api="$api -D__LINUX_OSS__" - AC_CHECK_LIB(ossaudio, main, , AC_MSG_ERROR([RtAudio requires the ossaudio library])) - AC_CHECK_LIB(pthread, pthread_create, , AC_MSG_ERROR([RtAudio requires the pthread library!])) - ]), - [*-*-linux*], [ - # Look for ALSA flag - AS_IF([test "x$with_alsa" = "xyes"], [ - AC_MSG_RESULT([using ALSA]) - api="$api -D__LINUX_ALSA__" - req="$req alsa" - AC_CHECK_LIB(asound, snd_pcm_open, , AC_MSG_ERROR([ALSA support requires the asound library!])) - ]) - # Look for PULSE flag - AS_IF([test "x$with_pulse" = "xyes"], [ - AC_MSG_RESULT([using PulseAudio]) - api="$api -D__LINUX_PULSE__" - req="$req libpulse-simple" - AC_CHECK_LIB(pulse-simple, pa_simple_flush, , AC_MSG_ERROR([PulseAudio support requires the pulse-simple library!])) - ]) +AS_CASE(["$systems"], [*" oss "*], [ + # libossaudio not required on some platforms (e.g. linux) so we + # don't break things if it's not found, but issue a warning when we + # are not sure (i.e. not on linux) + AS_CASE([$host], [*-*-linux*], [], [*], [need_ossaudio=yes]) + AC_CHECK_LIB(ossaudio, main, [have_ossaudio=true], + AS_CASE(["$required"], [*" oss "*], + AS_IF([test "x$need_ossaudio" = xyes], + AC_MSG_WARN([RtAudio may require the ossaudio library])))) - # Look for OSS flag - AS_IF([test "x$with_oss" = "xyes"], [ - AC_MSG_RESULT([using OSS]) - api="$api -D__LINUX_OSS__" - ]) + # linux systems may have soundcard.h but *not* have OSS4 installed, + # we have to actually check if it exports OSS4 symbols + AC_CHECK_DECL(SNDCTL_SYSINFO, + [api="$api -D__LINUX_OSS__" + need_pthread=yes + found="$found OSS"], + AS_CASE(["$required"], [*" oss "*], + AC_MSG_ERROR([sys/soundcard.h not found])) + [], + [#include ]) +]) - # If no audio api flags specified, use ALSA - AS_IF([test "x$api" = "x" ], [ - AC_MSG_RESULT([using ALSA]) - api="${api} -D__LINUX_ALSA__" - req="${req} alsa" - AC_CHECK_LIB(asound, snd_pcm_open, , AC_MSG_ERROR([ALSA support requires the asound library!])) - ]) - AC_CHECK_LIB(pthread, pthread_create, , AC_MSG_ERROR([RtAudio requires the pthread library!])) - ], - [*-apple*],[ - # Look for Core flag - AS_IF([test "x$with_core" = "xyes"], [ - AC_MSG_RESULT([using CoreAudio]) - api="$api -D__MACOSX_CORE__" - AC_CHECK_HEADER(CoreAudio/CoreAudio.h, [], [AC_MSG_ERROR([CoreAudio header files not found!])] ) - LIBS="$LIBS -framework CoreAudio -framework CoreFoundation" - ]) - # If no audio api flags specified, use CoreAudio - AS_IF([test "x$api" = "x" ], [ - AC_MSG_RESULT([using CoreAudio]) - api="${api} -D__MACOSX_CORE__" - AC_CHECK_HEADER(CoreAudio/CoreAudio.h, - [], - [AC_MSG_ERROR([CoreAudio header files not found!])] ) - LIBS="LIBS -framework CoreAudio -framework CoreFoundation" - ]) - AC_CHECK_LIB(pthread, pthread_create, , AC_MSG_ERROR([RtAudio requires the pthread library!])) - ], - [*-mingw32*],[ - AS_IF([test "x$with_asio" = "xyes" ], [ - AC_MSG_RESULT([using ASIO]) - api="$api -D__WINDOWS_ASIO__" - use_asio=yes - CPPFLAGS="-I$srcdir/include $CPPFLAGS" - ]) - # Look for DirectSound flag - AS_IF([test "x$with_ds" = "xyes" ], [ - AC_MSG_RESULT([using DirectSound]) - api="$api -D__WINDOWS_DS__" - LIBS="-ldsound -lwinmm $LIBS" - ]) - # Look for WASAPI flag - AS_IF([test "x$with_wasapi" = "xyes"], [ - AC_MSG_RESULT([using WASAPI]) - api="$api -D__WINDOWS_WASAPI__" - LIBS="-lwinmm -luuid -lksuser $LIBS" - CPPFLAGS="-I$srcdir/include $CPPFLAGS" - ]) - # If no audio api flags specified, use DS - AS_IF([test "x$api" = "x" ], [ - AC_MSG_RESULT([using DirectSound]) - api="$api -D__WINDOWS_DS__" - LIBS="-ldsound -lwinmm $LIBS" - ]) - LIBS="-lole32 $LIBS" - ],[ +AS_CASE(["$systems"], [*" jack "*], [ + AC_CHECK_LIB(jack, jack_client_open, + [api="$api -D__UNIX_JACK__" + req="$req jack" + need_pthread=yes + found="$found JACK" + LIBS="-ljack $LIBS"], + AS_CASE(["$required"], [*" jack "*], + AC_MSG_ERROR([JACK support requires the jack library!]))) +]) + +AS_CASE(["$systems"], [*" core "*], [ + AC_CHECK_HEADER(CoreAudio/CoreAudio.h, + [api="$api -D__MACOSX_CORE__" + need_pthread=yes + found="$found CoreAudio", + LIBS="$LIBS -framework CoreAudio -framework CoreFoundation"], + AS_CASE(["$required"], [*" core "*], + AC_MSG_ERROR([CoreAudio header files not found!]))) +]) + +AS_CASE(["$systems"], [*" asio "*], [ + api="$api -D__WINDOWS_ASIO__" + use_asio=yes + CPPFLAGS="-I$srcdir/include $CPPFLAGS" + need_ole32=yes + found="$found ASIO" +]) + +AS_CASE(["$systems"], [*" ds "*], [ + AC_CHECK_HEADERS(mmsystem.h mmreg.h dsound.h, + [api="$api -D__WINDOWS_DS__" + need_ole32=yes + found="$found DirectSound" + LIBS="-ldsound -lwinmm $LIBS"]) +]) + +AS_CASE(["$systems"], [*" wasapi "*], [ + AC_CHECK_HEADERS(windows.h audioclient.h avrt.h mmdeviceapi.h, + [api="$api -D__WINDOWS_WASAPI__" + CPPFLAGS="-I$srcdir/include $CPPFLAGS" + need_ole32=yes + found="$found WASAPI" + LIBS="-lwinmm -lksuser -lmfplat -lmfuuid -lwmcodecdspuuid $LIBS"]) +]) + +AS_IF([test -n "$need_ole32"], [LIBS="-lole32 $LIBS"]) + +AS_IF([test -n "$need_pthread"],[ + AC_MSG_CHECKING([for pthread]) + AC_CHECK_LIB(pthread, pthread_create, , + AC_MSG_ERROR([RtAudio requires the pthread library!]))]) + +AC_MSG_CHECKING([for audio API]) + +# Error case: no known realtime systems found. +AS_IF([test x"$api" = "x"], [ AC_MSG_RESULT([none]) - # Default case for unknown realtime systems. - AC_MSG_ERROR([Unknown system type for realtime support!]) - ] -) + AC_MSG_ERROR([No known system type found for realtime support!]) +], [ + AC_MSG_RESULT([$found]) +]) AM_CONDITIONAL( ASIO, [test "x${use_asio}" = "xyes" ]) diff --git a/contrib/go/rtaudio/rtaudio.go b/contrib/go/rtaudio/rtaudio.go index a0baf31..f487b14 100644 --- a/contrib/go/rtaudio/rtaudio.go +++ b/contrib/go/rtaudio/rtaudio.go @@ -15,7 +15,7 @@ package rtaudio #cgo jack LDFLAGS: -ljack #cgo windows CXXFLAGS: -D__WINDOWS_WASAPI__ -#cgo windows LDFLAGS: -lm -luuid -lksuser -lwinmm -lole32 -static +#cgo windows LDFLAGS: -lm -lksuser -lmfplat -lmfuuid -lwmcodecdspuuid -lwinmm -lole32 -static #cgo darwin CXXFLAGS: -D__MACOSX_CORE__ #cgo darwin LDFLAGS: -framework CoreAudio -framework CoreFoundation diff --git a/doc/doxygen/compiling.txt b/doc/doxygen/compiling.txt index 29dcbd4..819ba9c 100644 --- a/doc/doxygen/compiling.txt +++ b/doc/doxygen/compiling.txt @@ -80,8 +80,8 @@ In order to compile RtAudio for a specific OS and audio API, it is necessary to WASAPI RtApiWasapi __WINDOWS_WASAPI__ - MinGW: FunctionDiscoveryKeys_devpkey.h, lksuser, luuid, lwinmm, lole32 - MinGW: g++ -Wall -D__WINDOWS_WASAPI__ -Iinclude -o audioprobe audioprobe.cpp RtAudio.cpp -lole32 -lwinmm -lksuser -luuid + MinGW: FunctionDiscoveryKeys_devpkey.h, lksuser, lmfplat, lmfuuid, lwmcodecdspuuid, lwinmm, lole32 + MinGW: g++ -Wall -D__WINDOWS_WASAPI__ -Iinclude -o audioprobe audioprobe.cpp RtAudio.cpp -lole32 -lwinmm -lksuser -lmfplat -lmfuuid -lwmcodecdspuuid

diff --git a/doc/doxygen/probe.txt b/doc/doxygen/probe.txt index 975ff0e..6997000 100644 --- a/doc/doxygen/probe.txt +++ b/doc/doxygen/probe.txt @@ -45,7 +45,7 @@ The RtAudio::DeviceInfo structure is defined in RtAudio.h and provides a variety bool isDefaultOutput; // true if this is the default output device. bool isDefaultInput; // true if this is the default input device. std::vector sampleRates; // Supported sample rates. - unsigned int preferredSampleRate; // Preferred sample rate, eg. for WASAPI the system sample rate. + unsigned int preferredSampleRate; // Preferred sample rate, e.g. for WASAPI the system sample rate. RtAudioFormat nativeFormats; // Bit mask of supported data formats. }; \endcode diff --git a/install.txt b/install.txt index f63cf38..ea4e121 100644 --- a/install.txt +++ b/install.txt @@ -28,7 +28,7 @@ A few options can be passed to configure (or the autogen.sh script), including: Typing "./configure --help" will display all the available options. Note that you can provide more than one "--with-" flag to the configure script to enable multiple API support. -If you wish to use a different compiler than that selected by configure, specify that compiler in the command line (ex. to use CC): +If you wish to use a different compiler than that selected by configure, specify that compiler in the command line (e.g. to use CC): ./configure CXX=CC diff --git a/rtaudio.pc.in b/rtaudio.pc.in index 7627780..0e81090 100644 --- a/rtaudio.pc.in +++ b/rtaudio.pc.in @@ -1,4 +1,4 @@ -prefix=@CMAKE_INSTALL_PREFIX@ +prefix=@prefix@ exec_prefix=${prefix} libdir=${exec_prefix}/lib includedir=${prefix}/include/rtaudio diff --git a/rtaudio_c.cpp b/rtaudio_c.cpp index 699d2ce..da3ab24 100644 --- a/rtaudio_c.cpp +++ b/rtaudio_c.cpp @@ -15,40 +15,33 @@ struct rtaudio { char errmsg[MAX_ERROR_MESSAGE_LENGTH]; }; -static const rtaudio_api_t compiled_api[] = { -#if defined(__UNIX_JACK__) - RTAUDIO_API_UNIX_JACK, -#endif -#if defined(__LINUX_ALSA__) - RTAUDIO_API_LINUX_ALSA, -#endif -#if defined(__LINUX_PULSE__) - RTAUDIO_API_LINUX_PULSE, -#endif -#if defined(__LINUX_OSS__) - RTAUDIO_API_LINUX_OSS, -#endif -#if defined(__WINDOWS_ASIO__) - RTAUDIO_API_WINDOWS_ASIO, -#endif -#if defined(__WINDOWS_WASAPI__) - RTAUDIO_API_WINDOWS_WASAPI, -#endif -#if defined(__WINDOWS_DS__) - RTAUDIO_API_WINDOWS_DS, -#endif -#if defined(__MACOSX_CORE__) - RTAUDIO_API_MACOSX_CORE, -#endif -#if defined(__RTAUDIO_DUMMY__) - RTAUDIO_API_DUMMY, -#endif - RTAUDIO_API_UNSPECIFIED, -}; - const char *rtaudio_version() { return RTAUDIO_VERSION; } -const rtaudio_api_t *rtaudio_compiled_api() { return compiled_api; } +extern "C" const rtaudio_api_t rtaudio_compiled_apis[]; // casting from RtAudio::Api[] +extern "C" const unsigned int rtaudio_num_compiled_apis; +const rtaudio_api_t *rtaudio_compiled_api() { return rtaudio_compiled_apis; } + +extern "C" const char* rtaudio_api_names[][2]; +const char *rtaudio_api_name(rtaudio_api_t api) { + if (api < 0 || api >= RTAUDIO_API_NUM) + return NULL; + return rtaudio_api_names[api][0]; +} + +const char *rtaudio_api_display_name(rtaudio_api_t api) +{ + if (api < 0 || api >= RTAUDIO_API_NUM) + return "Unknown"; + return rtaudio_api_names[api][1]; +} + +rtaudio_api_t rtaudio_compiled_api_by_name(const char *name) { + RtAudio::Api api = RtAudio::UNSPECIFIED; + if (name) { + api = RtAudio::getCompiledApiByName(name); + } + return (rtaudio_api_t)api; +} const char *rtaudio_error(rtaudio_t audio) { if (audio->has_error) { diff --git a/rtaudio_c.h b/rtaudio_c.h index 05015a9..a366117 100644 --- a/rtaudio_c.h +++ b/rtaudio_c.h @@ -2,8 +2,12 @@ #define RTAUDIO_C_H #if defined(RTAUDIO_EXPORT) +#if defined _WIN32 || defined __CYGWIN__ #define RTAUDIOAPI __declspec(dllexport) #else +#define RTAUDIOAPI __attribute__((visibility("default"))) +#endif +#else #define RTAUDIOAPI //__declspec(dllimport) #endif @@ -64,6 +68,7 @@ typedef enum rtaudio_api { RTAUDIO_API_WINDOWS_ASIO, RTAUDIO_API_WINDOWS_DS, RTAUDIO_API_DUMMY, + RTAUDIO_API_NUM, } rtaudio_api_t; #define NUM_SAMPLE_RATES 16 @@ -102,6 +107,9 @@ typedef struct rtaudio *rtaudio_t; RTAUDIOAPI const char *rtaudio_version(void); RTAUDIOAPI const rtaudio_api_t *rtaudio_compiled_api(void); +RTAUDIOAPI const char *rtaudio_api_name(rtaudio_api_t api); +RTAUDIOAPI const char *rtaudio_api_display_name(rtaudio_api_t api); +RTAUDIOAPI rtaudio_api_t rtaudio_compiled_api_by_name(const char *name); RTAUDIOAPI const char *rtaudio_error(rtaudio_t audio); diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 0fb028f..5847027 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -3,23 +3,28 @@ if (WIN32) include_directories(../include) endif (WIN32) +list(GET LIB_TARGETS 0 LIBRTAUDIO) + add_executable(audioprobe audioprobe.cpp) -target_link_libraries(audioprobe rtaudio_static ${LINKLIBS}) +target_link_libraries(audioprobe ${LIBRTAUDIO} ${LINKLIBS}) add_executable(playsaw playsaw.cpp) -target_link_libraries(playsaw rtaudio_static ${LINKLIBS}) +target_link_libraries(playsaw ${LIBRTAUDIO} ${LINKLIBS}) add_executable(playraw playraw.cpp) -target_link_libraries(playraw rtaudio_static ${LINKLIBS}) +target_link_libraries(playraw ${LIBRTAUDIO} ${LINKLIBS}) add_executable(record record.cpp) -target_link_libraries(record rtaudio_static ${LINKLIBS}) +target_link_libraries(record ${LIBRTAUDIO} ${LINKLIBS}) add_executable(duplex duplex.cpp) -target_link_libraries(duplex rtaudio_static ${LINKLIBS}) +target_link_libraries(duplex ${LIBRTAUDIO} ${LINKLIBS}) + +add_executable(apinames apinames.cpp) +target_link_libraries(apinames ${LIBRTAUDIO} ${LINKLIBS}) add_executable(testall testall.cpp) -target_link_libraries(testall rtaudio_static ${LINKLIBS}) +target_link_libraries(testall ${LIBRTAUDIO} ${LINKLIBS}) add_executable(teststops teststops.cpp) -target_link_libraries(teststops rtaudio_static ${LINKLIBS}) +target_link_libraries(teststops ${LIBRTAUDIO} ${LINKLIBS}) diff --git a/tests/Makefile.am b/tests/Makefile.am index e39fdde..c8159da 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -1,5 +1,5 @@ -noinst_PROGRAMS = audioprobe playsaw playraw record duplex testall teststops +noinst_PROGRAMS = audioprobe playsaw playraw record duplex apinames testall teststops AM_CXXFLAGS = -Wall -I$(top_srcdir) @@ -18,6 +18,9 @@ record_LDADD = $(top_builddir)/librtaudio.la duplex_SOURCES = duplex.cpp duplex_LDADD = $(top_builddir)/librtaudio.la +apinames_SOURCES = apinames.cpp +apinames_LDADD = $(top_builddir)/librtaudio.la + testall_SOURCES = testall.cpp testall_LDADD = $(top_builddir)/librtaudio.la diff --git a/tests/apinames.cpp b/tests/apinames.cpp new file mode 100644 index 0000000..c270764 --- /dev/null +++ b/tests/apinames.cpp @@ -0,0 +1,157 @@ +/******************************************/ +/* + apinames.cpp + by Jean Pierre Cimalando, 2018. + + This program tests parts of RtAudio related + to API names, the conversion from name to API + and vice-versa. +*/ +/******************************************/ + +#include "RtAudio.h" +#include +#include +#include + +int test_cpp() { + std::vector apis; + RtAudio::getCompiledApi( apis ); + + // ensure the known APIs return valid names + std::cout << "API names by identifier (C++):\n"; + for ( size_t i = 0; i < apis.size() ; ++i ) { + const std::string name = RtAudio::getApiName(apis[i]); + if (name.empty()) { + std::cout << "Invalid name for API " << (int)apis[i] << "\n"; + exit(1); + } + const std::string displayName = RtAudio::getApiDisplayName(apis[i]); + if (displayName.empty()) { + std::cout << "Invalid display name for API " << (int)apis[i] << "\n"; + exit(1); + } + std::cout << "* " << (int)apis[i] << " '" << name << "': '" << displayName << "'\n"; + } + + // ensure unknown APIs return the empty string + { + const std::string name = RtAudio::getApiName((RtAudio::Api)-1); + if (!name.empty()) { + std::cout << "Bad string for invalid API '" << name << "'\n"; + exit(1); + } + const std::string displayName = RtAudio::getApiDisplayName((RtAudio::Api)-1); + if (displayName!="Unknown") { + std::cout << "Bad display string for invalid API '" << displayName << "'\n"; + exit(1); + } + } + + // try getting API identifier by name + std::cout << "API identifiers by name (C++):\n"; + for ( size_t i = 0; i < apis.size() ; ++i ) { + std::string name = RtAudio::getApiName(apis[i]); + if ( RtAudio::getCompiledApiByName(name) != apis[i] ) { + std::cout << "Bad identifier for API '" << name << "'\n"; + exit( 1 ); + } + std::cout << "* '" << name << "': " << (int)apis[i] << "\n"; + + for ( size_t j = 0; j < name.size(); ++j ) + name[j] = (j & 1) ? toupper(name[j]) : tolower(name[j]); + RtAudio::Api api = RtAudio::getCompiledApiByName(name); + if ( api != RtAudio::UNSPECIFIED ) { + std::cout << "Identifier " << (int)api << " for invalid API '" << name << "'\n"; + exit( 1 ); + } + } + + // try getting an API identifier by unknown name + { + RtAudio::Api api; + api = RtAudio::getCompiledApiByName(""); + if ( api != RtAudio::UNSPECIFIED ) { + std::cout << "Bad identifier for unknown API name\n"; + exit( 1 ); + } + } + + return 0; +} + +#include "rtaudio_c.h" + +int test_c() { + const rtaudio_api_t *apis = rtaudio_compiled_api(); + + // ensure the known APIs return valid names + std::cout << "API names by identifier (C):\n"; + for ( size_t i = 0; apis[i] != RTAUDIO_API_UNSPECIFIED; ++i) { + const std::string name = rtaudio_api_name(apis[i]); + if (name.empty()) { + std::cout << "Invalid name for API " << (int)apis[i] << "\n"; + exit(1); + } + const std::string displayName = rtaudio_api_display_name(apis[i]); + if (displayName.empty()) { + std::cout << "Invalid display name for API " << (int)apis[i] << "\n"; + exit(1); + } + std::cout << "* " << (int)apis[i] << " '" << name << "': '" << displayName << "'\n"; + } + + // ensure unknown APIs return the empty string + { + const char *s = rtaudio_api_name((rtaudio_api_t)-1); + const std::string name(s?s:""); + if (!name.empty()) { + std::cout << "Bad string for invalid API '" << name << "'\n"; + exit(1); + } + s = rtaudio_api_display_name((rtaudio_api_t)-1); + const std::string displayName(s?s:""); + if (displayName!="Unknown") { + std::cout << "Bad display string for invalid API '" << displayName << "'\n"; + exit(1); + } + } + + // try getting API identifier by name + std::cout << "API identifiers by name (C):\n"; + for ( size_t i = 0; apis[i] != RTAUDIO_API_UNSPECIFIED ; ++i ) { + const char *s = rtaudio_api_name(apis[i]); + std::string name(s?s:""); + if ( rtaudio_compiled_api_by_name(name.c_str()) != apis[i] ) { + std::cout << "Bad identifier for API '" << name << "'\n"; + exit( 1 ); + } + std::cout << "* '" << name << "': " << (int)apis[i] << "\n"; + + for ( size_t j = 0; j < name.size(); ++j ) + name[j] = (j & 1) ? toupper(name[j]) : tolower(name[j]); + rtaudio_api_t api = rtaudio_compiled_api_by_name(name.c_str()); + if ( api != RTAUDIO_API_UNSPECIFIED ) { + std::cout << "Identifier " << (int)api << " for invalid API '" << name << "'\n"; + exit( 1 ); + } + } + + // try getting an API identifier by unknown name + { + rtaudio_api_t api; + api = rtaudio_compiled_api_by_name(""); + if ( api != RTAUDIO_API_UNSPECIFIED ) { + std::cout << "Bad identifier for unknown API name\n"; + exit( 1 ); + } + } + + return 0; +} + +int main() +{ + test_cpp(); + test_c(); +} diff --git a/tests/audioprobe.cpp b/tests/audioprobe.cpp index 1b93908..c83d3a5 100644 --- a/tests/audioprobe.cpp +++ b/tests/audioprobe.cpp @@ -46,6 +46,7 @@ int main() info = audio.getDeviceInfo(i); std::cout << "\nDevice Name = " << info.name << '\n'; + std::cout << "Device ID = " << i << '\n'; if ( info.probed == false ) std::cout << "Probe Status = UNsuccessful\n"; else {