diff --git a/3rdparty/portaudio/CMakeLists.txt b/3rdparty/portaudio/CMakeLists.txt index 3d81062882d29..fff8af999519a 100644 --- a/3rdparty/portaudio/CMakeLists.txt +++ b/3rdparty/portaudio/CMakeLists.txt @@ -15,6 +15,33 @@ else() set(LIBRARY_BUILD_TYPE STATIC) endif() +option(PA_WARNINGS_ARE_ERRORS "Turn compiler warnings into errors" OFF) +if(PA_WARNINGS_ARE_ERRORS) + if(MSVC) + add_compile_options(/WX + # "Grandfathered" warnings that existed before we started enforcement. + # Do *NOT* add warnings to this list. Instead, fix your code so that it doesn't produce the warning. + # TODO: fix the offending code so that we don't have to exclude specific warnings anymore. + /wd4244 # W2 conversion possible loss of data + /wd4267 # W3 conversion possible loss of data + /wd4996 # W3 unsafe/deprecated + ) + else() + add_compile_options(-Werror + # "Grandfathered" warnings that existed before we started enforcement. + # Do *NOT* add warnings to this list. Instead, fix your code so that it doesn't produce the warning. + # TODO: fix the offending code so that we don't have to exclude specific warnings anymore. + -Wno-error=deprecated-declarations # https://github.com/PortAudio/portaudio/issues/213 https://github.com/PortAudio/portaudio/issues/641 + -Wno-error=stringop-overflow + ) + if (CMAKE_C_COMPILER_ID MATCHES "Clang") + # Don't fail on older clang versions that don't recognize the latest warnings in the list above. + # Note that unrecognized warning options are not a fatal error on GCC, and in fact, GCC will choke on this option. Hence the conditional. + add_compile_options(-Wno-error=unknown-warning-option) + endif() + endif() +endif() + add_library(PortAudio ${LIBRARY_BUILD_TYPE} src/common/pa_allocation.c @@ -130,6 +157,8 @@ if(WIN32) src/os/win/pa_win_hostapis.c src/os/win/pa_win_util.c src/os/win/pa_win_util.h + src/os/win/pa_win_version.c + src/os/win/pa_win_version.h src/os/win/pa_win_waveformat.c src/os/win/pa_win_wdmks_utils.h src/os/win/pa_x86_plain_converters.h @@ -278,17 +307,13 @@ elseif(UNIX) target_include_directories(PortAudio PRIVATE src/hostapi/coreaudio) set(PORTAUDIO_PUBLIC_HEADERS "${PORTAUDIO_PUBLIC_HEADERS}" include/pa_mac_core.h) - find_library(COREAUDIO_LIBRARY CoreAudio REQUIRED) - find_library(AUDIOTOOLBOX_LIBRARY AudioToolbox REQUIRED) - find_library(AUDIOUNIT_LIBRARY AudioUnit REQUIRED) - find_library(COREFOUNDATION_LIBRARY CoreFoundation REQUIRED) - find_library(CORESERVICES_LIBRARY CoreServices REQUIRED) - target_link_libraries(PortAudio PRIVATE - "${COREAUDIO_LIBRARY}" - "${AUDIOTOOLBOX_LIBRARY}" - "${AUDIOUNIT_LIBRARY}" - "${COREFOUNDATION_LIBRARY}" - "${CORESERVICES_LIBRARY}" + target_link_libraries(PortAudio + PRIVATE + -Wl,-framework,CoreAudio + -Wl,-framework,AudioToolbox + -Wl,-framework,AudioUnit + -Wl,-framework,CoreFoundation + -Wl,-framework,CoreServices ) target_compile_definitions(PortAudio PUBLIC PA_USE_COREAUDIO=1) set(PKGCONFIG_CFLAGS "${PKGCONFIG_CFLAGS} -DPA_USE_COREAUDIO=1") @@ -340,6 +365,24 @@ elseif(UNIX) target_compile_definitions(PortAudio PUBLIC PA_USE_AUDIOIO=1) set(PKGCONFIG_CFLAGS "${PKGCONFIG_CFLAGS} -DPA_USE_AUDIOIO=1") endif() + + find_package(PulseAudio) + cmake_dependent_option(PA_USE_PULSEAUDIO "Enable support for PulseAudio general purpose sound server" ON PulseAudio_FOUND OFF) + if(PA_USE_PULSEAUDIO) + target_link_libraries(PortAudio PRIVATE PulseAudio::PulseAudio) + target_sources(PortAudio PRIVATE + src/hostapi/pulseaudio/pa_linux_pulseaudio_block.c + src/hostapi/pulseaudio/pa_linux_pulseaudio.c + src/hostapi/pulseaudio/pa_linux_pulseaudio_cb.c) + + target_compile_definitions(PortAudio PUBLIC PA_USE_PULSEAUDIO=1) + set(PKGCONFIG_CFLAGS "${PKGCONFIG_CFLAGS} -DPA_USE_PULSEAUDIO=1") + set(PKGCONFIG_REQUIRES_PRIVATE "${PKGCONFIG_REQUIRES_PRIVATE} libpulse") + + # needed for PortAudioConfig.cmake so `find_package(PortAudio)` works in downstream projects + install(FILES cmake/modules/FindPulseAudio.cmake DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/portaudio/modules") + endif() + endif() endif() diff --git a/3rdparty/portaudio/Makefile.in b/3rdparty/portaudio/Makefile.in index ef5f2d85709f2..05597cc23db0e 100644 --- a/3rdparty/portaudio/Makefile.in +++ b/3rdparty/portaudio/Makefile.in @@ -44,7 +44,7 @@ PALIB = libportaudio.la PAINC = include/portaudio.h PA_LDFLAGS = $(LDFLAGS) $(SHARED_FLAGS) -rpath $(libdir) -no-undefined \ - -export-symbols-regex "(Pa|PaMacCore|PaJack|PaAlsa|PaAsio|PaOSS|PaWasapi|PaWasapiWinrt|PaWinMME)_.*" \ + -export-symbols-regex "(Pa|PaMacCore|PaPulseAudio|PaJack|PaAlsa|PaAsio|PaOSS|PaWasapi|PaWasapiWinrt|PaWinMME)_.*" \ -version-info $(LT_CURRENT):$(LT_REVISION):$(LT_AGE) COMMON_OBJS = \ @@ -66,7 +66,7 @@ LOOPBACK_OBJS = \ qa/loopback/src/test_audio_analyzer.o \ qa/loopback/src/write_wav.o \ qa/loopback/src/paqa.o - + EXAMPLES = \ bin/pa_devs \ bin/pa_fuzz \ @@ -82,7 +82,7 @@ SELFTESTS = \ bin/paqa_devs \ bin/paqa_errs \ bin/paqa_latency - + TESTS = \ bin/patest1 \ bin/patest_buffer \ @@ -146,6 +146,7 @@ SRC_DIRS = \ src/hostapi/coreaudio \ src/hostapi/dsound \ src/hostapi/jack \ + src/hostapi/pulseaudio \ src/hostapi/oss \ src/hostapi/skeleton \ src/hostapi/wasapi \ diff --git a/3rdparty/portaudio/README.md b/3rdparty/portaudio/README.md index cb33144cc12c0..c280f7089b1bb 100644 --- a/3rdparty/portaudio/README.md +++ b/3rdparty/portaudio/README.md @@ -41,6 +41,7 @@ Please feel free to join. See http://www.portaudio.com for details. src/hostapi/dsound = Windows Direct Sound src/hostapi/jack = JACK Audio Connection Kit src/hostapi/oss = Unix Open Sound System (OSS) + src/hostapi/pulseaudio = Sound system for POSIX OSes src/hostapi/wasapi = Windows Vista WASAPI src/hostapi/wdmks = Windows WDM Kernel Streaming src/hostapi/wmme = Windows MultiMedia Extensions (MME) diff --git a/3rdparty/portaudio/bindings/cpp/CMakeLists.txt b/3rdparty/portaudio/bindings/cpp/CMakeLists.txt new file mode 100644 index 0000000000000..488bac82cd037 --- /dev/null +++ b/3rdparty/portaudio/bindings/cpp/CMakeLists.txt @@ -0,0 +1,122 @@ +cmake_minimum_required(VERSION 3.13) +cmake_policy(VERSION 3.13) + +project(PortAudioCpp VERSION 19.8 LANGUAGES CXX) +list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules") + +# Todo (multi-generator): Add support for multiple generators like: - {Debug, +# Release} x {Static, Dynamic} x {MT, MD (Windows only)} + +# ############################################################################## +# sources and headers +# ############################################################################## + +set(portaudiocpp-sources + source/portaudiocpp/BlockingStream.cxx + source/portaudiocpp/CFunCallbackStream.cxx + source/portaudiocpp/CallbackInterface.cxx + source/portaudiocpp/CallbackStream.cxx + source/portaudiocpp/CppFunCallbackStream.cxx + source/portaudiocpp/Device.cxx + source/portaudiocpp/DirectionSpecificStreamParameters.cxx + source/portaudiocpp/Exception.cxx + source/portaudiocpp/HostApi.cxx + source/portaudiocpp/InterfaceCallbackStream.cxx + source/portaudiocpp/MemFunCallbackStream.cxx + source/portaudiocpp/Stream.cxx + source/portaudiocpp/StreamParameters.cxx + source/portaudiocpp/System.cxx + source/portaudiocpp/SystemDeviceIterator.cxx + source/portaudiocpp/SystemHostApiIterator.cxx) + +# since we don't GLOBing this variable must be kept up to date otherwise user +# installations are broken. +set(portaudiocpp-header-files + include/portaudiocpp/AutoSystem.hxx + include/portaudiocpp/BlockingStream.hxx + include/portaudiocpp/CFunCallbackStream.hxx + include/portaudiocpp/CallbackInterface.hxx + include/portaudiocpp/CallbackStream.hxx + include/portaudiocpp/CppFunCallbackStream.hxx + include/portaudiocpp/Device.hxx + include/portaudiocpp/DirectionSpecificStreamParameters.hxx + include/portaudiocpp/Exception.hxx + include/portaudiocpp/HostApi.hxx + include/portaudiocpp/InterfaceCallbackStream.hxx + include/portaudiocpp/MemFunCallbackStream.hxx + include/portaudiocpp/PortAudioCpp.hxx + include/portaudiocpp/SampleDataFormat.hxx + include/portaudiocpp/Stream.hxx + include/portaudiocpp/StreamParameters.hxx + include/portaudiocpp/System.hxx + include/portaudiocpp/SystemDeviceIterator.hxx + include/portaudiocpp/SystemHostApiIterator.hxx) + +if(WIN32) + find_package(ASIO MODULE) + if(ASIO_FOUND) + list(APPEND portaudiocpp-sources source/portaudiocpp/AsioDeviceAdapter.cxx) + list(APPEND portaudiocpp-header-files + include/portaudiocpp/AsioDeviceAdapter.hxx) + endif() +endif() + +# ############################################################################## +# portaudiocpp-targets +# ############################################################################## + +add_library(portaudiocpp ${portaudiocpp-sources}) +add_library(PortAudio::portaudiocpp ALIAS portaudiocpp) # For subdirectory build + +find_package(PortAudio MODULE REQUIRED) + +target_link_libraries(portaudiocpp PUBLIC PortAudio::portaudio) +target_include_directories( + portaudiocpp PUBLIC $ + $) +set_target_properties(portaudiocpp PROPERTIES SOVERSION 2) +# Todo (modernize): update the code at least to c++14 +# target_compile_features(portaudiocpp PUBLIC cxx_std_14) + +# ## Export ### +include(GNUInstallDirs) + +install( + TARGETS portaudiocpp + EXPORT PortAudioCppTargets + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + INCLUDES + DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/portaudiocpp) + +install(FILES ${portaudiocpp-header-files} + DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/portaudiocpp) + +install( + EXPORT PortAudioCppTargets + FILE PortAudioCppTargets.cmake + NAMESPACE PortAudio:: + DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/PortAudio) + +include(CMakePackageConfigHelpers) +configure_package_config_file( + ${CMAKE_CURRENT_SOURCE_DIR}/cmake/PortAudioCppConfig.cmake.in + "${CMAKE_CURRENT_BINARY_DIR}/PortAudioCppConfig.cmake" + INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/PortAudio) + +write_basic_package_version_file( + "${CMAKE_CURRENT_BINARY_DIR}/PortAudioCppConfigVersion.cmake" + COMPATIBILITY SameMajorVersion +) + +install(FILES "${CMAKE_CURRENT_BINARY_DIR}/PortAudioCppConfig.cmake" + "${CMAKE_CURRENT_BINARY_DIR}/PortAudioCppConfigVersion.cmake" + DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/PortAudio) + +#use relative path, since CMAKE can't reconfigure on install with different prefix path +set(PC_PREFIX "\${pcfiledir}/../..") +configure_file(cmake/portaudiocpp.pc.in "${CMAKE_CURRENT_BINARY_DIR}/portaudiocpp.pc" @ONLY) +install(FILES "${CMAKE_CURRENT_BINARY_DIR}/portaudiocpp.pc" + CONFIGURATIONS Release RelWithDebInfo + DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig) diff --git a/3rdparty/portaudio/bindings/cpp/cmake/PortAudioCppConfig.cmake.in b/3rdparty/portaudio/bindings/cpp/cmake/PortAudioCppConfig.cmake.in new file mode 100644 index 0000000000000..37540c0431eab --- /dev/null +++ b/3rdparty/portaudio/bindings/cpp/cmake/PortAudioCppConfig.cmake.in @@ -0,0 +1,5 @@ +@PACKAGE_INIT@ + +include("${CMAKE_CURRENT_LIST_DIR}/PortAudioCppTargets.cmake") + +check_required_components(PortAudioCpp) diff --git a/3rdparty/portaudio/bindings/cpp/cmake/modules/FindASIO.cmake b/3rdparty/portaudio/bindings/cpp/cmake/modules/FindASIO.cmake new file mode 100644 index 0000000000000..4a8d10d050776 --- /dev/null +++ b/3rdparty/portaudio/bindings/cpp/cmake/modules/FindASIO.cmake @@ -0,0 +1,81 @@ +#[=======================================================================[.rst: +FindASIO +-------- + +Finds the ASIO SDK by searching for the SDK ZIP in CMAKE_PREFIX_PATH and +CMAKE_CURRENT_BINARY_DIR. Alternatively, you may manually specify the path of +the SDK ZIP with the ASIO_SDK_ZIP_PATH variable, which can be used for caching +in CI scripts. + +If the ZIP is found, this module extracts it. +The ZIP extraction is skipped if the unzipped SDK is found. + +This module provides an `ASIO::host` IMPORT library target for building host +applications which use ASIO drivers. If you want to build an ASIO driver, this +module may serve as a useful start but you will need to modify it. + +#]=======================================================================] + +if(NOT WIN32) + message(WARNING "ASIO is only supported on Windows.") + set(ASIO_FOUND OFF) + return() +endif() + +file(GLOB HEADER_FILE + "${CMAKE_CURRENT_BINARY_DIR}/asiosdk*/common/asio.h" + "${CMAKE_PREFIX_PATH}/asiosdk*/common/asio.h" + # The old build systems before PortAudio 19.8 used to look for the ASIO SDK + # in the same parent directory as the source code repository. This is no + # longer advised or documented but kept for backwards compatibility. + "${CMAKE_CURRENT_SOURCE_DIR}/../asiosdk*/common/asio.h" +) +if(NOT EXISTS "${HEADER_FILE}") + # The file(ARCHIVE_EXTRACT) command was added in CMake 3.18, so if using an + # older version of CMake, the user needs to extract it themselves. + if(CMAKE_VERSION VERSION_LESS 3.18) + message(STATUS "ASIO SDK NOT found. Download the ASIO SDK ZIP from " + "https://www.steinberg.net/asiosdk and extract it to " + "${CMAKE_PREFIX_PATH} or ${CMAKE_CURRENT_BINARY_DIR}" + ) + return() + endif() + file(GLOB results + "${ASIO_SDK_ZIP_PATH}" + "${CMAKE_CURRENT_BINARY_DIR}/asiosdk*.zip" + "${CMAKE_PREFIX_PATH}/asiosdk*.zip" + "${CMAKE_CURRENT_SOURCE_DIR}/../asiosdk*.zip" + ) + foreach(f ${results}) + if(EXISTS "${f}") + message(STATUS "Extracting ASIO SDK ZIP archive: ${f}") + file(ARCHIVE_EXTRACT INPUT "${f}" DESTINATION "${CMAKE_CURRENT_BINARY_DIR}") + endif() + endforeach() + file(GLOB HEADER_FILE "${CMAKE_CURRENT_BINARY_DIR}/asiosdk*/common/asio.h") +endif() + +get_filename_component(HEADER_DIR "${HEADER_FILE}" DIRECTORY) +get_filename_component(ASIO_ROOT "${HEADER_DIR}" DIRECTORY) + +if(ASIO_ROOT) + set(ASIO_FOUND TRUE) + message(STATUS "Found ASIO SDK: ${ASIO_ROOT}") + + if(ASIO_FOUND AND NOT TARGET ASIO::host) + add_library(ASIO::host INTERFACE IMPORTED) + target_sources(ASIO::host INTERFACE + "${ASIO_ROOT}/common/asio.cpp" + "${ASIO_ROOT}/host/asiodrivers.cpp" + "${ASIO_ROOT}/host/pc/asiolist.cpp" + ) + target_include_directories(ASIO::host INTERFACE + "${ASIO_ROOT}/common" + "${ASIO_ROOT}/host" + "${ASIO_ROOT}/host/pc" + ) + target_link_libraries(ASIO::host INTERFACE ole32 uuid) + endif() +else() + message(STATUS "ASIO SDK NOT found") +endif() diff --git a/3rdparty/portaudio/bindings/cpp/cmake/modules/FindPortAudio.cmake b/3rdparty/portaudio/bindings/cpp/cmake/modules/FindPortAudio.cmake new file mode 100644 index 0000000000000..5d58c158bed8e --- /dev/null +++ b/3rdparty/portaudio/bindings/cpp/cmake/modules/FindPortAudio.cmake @@ -0,0 +1,39 @@ + +macro(handle_default) + +endmacro() + +if(TARGET PortAudio::portaudio) + # nothing to do + return() +endif() +# search for portaudio as cmake module +find_package(PortAudio CONFIG QUIET) +if(PortAudio_FOUND) + if(TARGET PortAudio::portaudio) + return() + elseif(TARGET portaudio) + # vcpkg and old portaudio installations do not provide the same targets + add_library(PortAudio::portaudio ALIAS portaudio) + return() + else() + message(FATAL_ERROR "PortAudio_FOUND but not target PortAudio::portaudio") + endif() +endif() + +# search for portaudio via pkg-config + +message(STATUS "portaudio could not be found via cmake, specify PortAudio_DIR.\n Searching for it via pkg-config") +find_package(PkgConfig REQUIRED) +pkg_check_modules(portaudio REQUIRED QUIET IMPORTED_TARGET GLOBAL portaudio-2.0) +add_library(PortAudio::portaudio ALIAS PkgConfig::portaudio) +return() + +# include(FindPackageHandleStandardArgs) +# find_package_handle_standard_args(Foo +# FOUND_VAR Foo_FOUND +# REQUIRED_VARS +# Foo_LIBRARY +# Foo_INCLUDE_DIR +# VERSION_VAR Foo_VERSION +# ) diff --git a/3rdparty/portaudio/bindings/cpp/cmake/portaudiocpp.pc.in b/3rdparty/portaudio/bindings/cpp/cmake/portaudiocpp.pc.in new file mode 100644 index 0000000000000..69333b278d534 --- /dev/null +++ b/3rdparty/portaudio/bindings/cpp/cmake/portaudiocpp.pc.in @@ -0,0 +1,12 @@ +prefix=@PC_PREFIX@ +exec_prefix=${prefix} +libdir=${prefix}/lib +includedir=${prefix}/include + +Name: PortAudioCpp +Description: Portable audio I/O C++ bindings +Version: 12 +Requires: portaudio-2.0 + +Libs: -L${libdir} -lportaudiocpp +Cflags: -I${includedir} diff --git a/3rdparty/portaudio/bindings/cpp/source/portaudiocpp/CMakeLists.txt b/3rdparty/portaudio/bindings/cpp/source/portaudiocpp/CMakeLists.txt new file mode 100644 index 0000000000000..cd4954191d972 --- /dev/null +++ b/3rdparty/portaudio/bindings/cpp/source/portaudiocpp/CMakeLists.txt @@ -0,0 +1,26 @@ +find_package(ASIO) +if(ASIO_FOUND) + set(portaudio-cpp-sources-asio AsioDeviceAdapter.cxx PARENT_SCOPE) +else() + set(portaudio-cpp-sources-asio PARENT_SCOPE) +endif() + +set(portaudio-cpp-sources + ${CMAKE_CURRENT_SOURCE_DIR}/BlockingStream.cxx + ${CMAKE_CURRENT_SOURCE_DIR}/CFunCallbackStream.cxx + ${CMAKE_CURRENT_SOURCE_DIR}/CallbackInterface.cxx + ${CMAKE_CURRENT_SOURCE_DIR}/CallbackStream.cxx + ${CMAKE_CURRENT_SOURCE_DIR}/CppFunCallbackStream.cxx + ${CMAKE_CURRENT_SOURCE_DIR}/Device.cxx + ${CMAKE_CURRENT_SOURCE_DIR}/DirectionSpecificStreamParameters.cxx + ${CMAKE_CURRENT_SOURCE_DIR}/Exception.cxx + ${CMAKE_CURRENT_SOURCE_DIR}/HostApi.cxx + ${CMAKE_CURRENT_SOURCE_DIR}/InterfaceCallbackStream.cxx + ${CMAKE_CURRENT_SOURCE_DIR}/MemFunCallbackStream.cxx + ${CMAKE_CURRENT_SOURCE_DIR}/Stream.cxx + ${CMAKE_CURRENT_SOURCE_DIR}/StreamParameters.cxx + ${CMAKE_CURRENT_SOURCE_DIR}/System.cxx + ${CMAKE_CURRENT_SOURCE_DIR}/SystemDeviceIterator.cxx + ${CMAKE_CURRENT_SOURCE_DIR}/SystemHostApiIterator.cxx + PARENT_SCOPE +) diff --git a/3rdparty/portaudio/cmake/modules/FindPulseAudio.cmake b/3rdparty/portaudio/cmake/modules/FindPulseAudio.cmake new file mode 100644 index 0000000000000..234692c4393c2 --- /dev/null +++ b/3rdparty/portaudio/cmake/modules/FindPulseAudio.cmake @@ -0,0 +1,147 @@ +# Copyright 2008 Matthias Kretz +# Copyright 2009 Marcus Hufgard +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# 3. Neither the name of the copyright holder nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED +# TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; +# OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR +# OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +# EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# +# SPDX-FileCopyrightText: 2008 Matthias Kretz +# SPDX-FileCopyrightText: 2009 Marcus Hufgard +# +# SPDX-License-Identifier: BSD-3-Clause + +#.rst: +# FindPulseAudio +# -------------- +# +# This is base on +# https://invent.kde.org/frameworks/extra-cmake-modules/-/blob/master/find-modules/FindPulseAudio.cmake +# +# Try to locate the PulseAudio library. +# If found, this will define the following variables: +# +# ``PulseAudio_FOUND`` +# True if the system has the PulseAudio library of at least +# the minimum version specified by either the version parameter +# to find_package() or the variable PulseAudio_MINIMUM_VERSION +# ``PulseAudio_INCLUDE_DIRS`` +# The PulseAudio include directory +# ``PulseAudio_LIBRARIES`` +# The PulseAudio libraries for linking +# ``PulseAudio_MAINLOOP_LIBRARY`` +# The libraries needed to use PulseAudio Mainloop +# ``PulseAudio_VERSION`` +# The version of PulseAudio that was found +# ``PulseAudio_INCLUDE_DIR`` +# Deprecated, use ``PulseAudio_INCLUDE_DIRS`` +# ``PulseAudio_LIBRARY`` +# Deprecated, use ``PulseAudio_LIBRARIES`` +# +# If ``PulseAudio_FOUND`` is TRUE, it will also define the following +# imported target: +# +# ``PulseAudio::PulseAudio`` +# The PulseAudio library +# +# Since 5.41.0. + +# Support PulseAudio_MINIMUM_VERSION for compatibility: +if(NOT PulseAudio_FIND_VERSION) + set(PulseAudio_FIND_VERSION "${PulseAudio_MINIMUM_VERSION}") +endif() + +# the minimum version of PulseAudio we require +if(NOT PulseAudio_FIND_VERSION) + set(PulseAudio_FIND_VERSION "1.0.0") +endif() + +find_package(PkgConfig) +pkg_check_modules(PC_PulseAudio QUIET libpulse>=${PulseAudio_FIND_VERSION}) +pkg_check_modules(PC_PulseAudio_MAINLOOP QUIET libpulse-mainloop-glib) + +find_path(PulseAudio_INCLUDE_DIRS pulse/pulseaudio.h + HINTS + ${PC_PulseAudio_INCLUDEDIR} + ${PC_PulseAudio_INCLUDE_DIRS} + ) + +find_library(PulseAudio_LIBRARIES NAMES pulse libpulse + HINTS + ${PC_PulseAudio_LIBDIR} + ${PC_PulseAudio_LIBRARY_DIRS} + ) + +find_library(PulseAudio_MAINLOOP_LIBRARY + NAMES pulse-mainloop pulse-mainloop-glib libpulse-mainloop-glib + HINTS + ${PC_PulseAudio_LIBDIR} + ${PC_PulseAudio_LIBRARY_DIRS} + ) + +# Store the version number in the cache, +# so we don't have to search every time again: +if(PulseAudio_INCLUDE_DIRS AND NOT PulseAudio_VERSION) + + # get PulseAudio's version from its version.h + file(STRINGS "${PulseAudio_INCLUDE_DIRS}/pulse/version.h" pulse_version_h + REGEX ".*pa_get_headers_version\\(\\).*") + string(REGEX REPLACE ".*pa_get_headers_version\\(\\)\ \\(\"([0-9]+\\.[0-9]+\\.[0-9]+)[^\"]*\"\\).*" "\\1" + _PulseAudio_VERSION "${pulse_version_h}") + + set(PulseAudio_VERSION "${_PulseAudio_VERSION}" + CACHE STRING "Version number of PulseAudio" + FORCE) +endif() + +# Use the new extended syntax of +# find_package_handle_standard_args(), +# which also handles version checking: +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(PulseAudio + REQUIRED_VARS PulseAudio_LIBRARIES + PulseAudio_INCLUDE_DIRS + VERSION_VAR PulseAudio_VERSION) + +# Deprecated synonyms +set(PulseAudio_INCLUDE_DIR "${PulseAudio_INCLUDE_DIRS}") +set(PulseAudio_LIBRARY "${PulseAudio_LIBRARIES}") +set(PulseAudio_MAINLOOP_LIBRARY "${PulseAudio_MAINLOOP_LIBRARY}") +set(PulseAudio_FOUND "${PulseAudio_FOUND}") + +if(PulseAudio_FOUND AND NOT TARGET PulseAudio::PulseAudio) + add_library(PulseAudio::PulseAudio UNKNOWN IMPORTED) + set_target_properties(PulseAudio::PulseAudio PROPERTIES + IMPORTED_LOCATION "${PulseAudio_LIBRARIES}" + INTERFACE_INCLUDE_DIRECTORIES "${PulseAudio_INCLUDE_DIRS}") +endif() + +mark_as_advanced(PulseAudio_INCLUDE_DIRS PulseAudio_INCLUDE_DIR + PulseAudio_LIBRARIES PulseAudio_LIBRARY + PulseAudio_MAINLOOP_LIBRARY PulseAudio_MAINLOOP_LIBRARY) + +include(FeatureSummary) +set_package_properties(PulseAudio PROPERTIES + URL "https://www.freedesktop.org/wiki/Software/PulseAudio" + DESCRIPTION "Sound server, for sound stream routing and mixing") diff --git a/3rdparty/portaudio/configure.in b/3rdparty/portaudio/configure.in index d3f4fe1983919..af08444266a4e 100644 --- a/3rdparty/portaudio/configure.in +++ b/3rdparty/portaudio/configure.in @@ -41,6 +41,10 @@ AC_ARG_WITH(jack, AS_HELP_STRING([--with-jack], [Enable support for JACK @<:@autodetect@:>@]), [with_jack=$withval]) +AC_ARG_WITH(pulseaudio, + AS_HELP_STRING([--with-pulseaudio], [Enable support for PulseAudio @<:@autodetect@:>@]), + [with_pulseaudio=$withval]) + AC_ARG_WITH(oss, AS_HELP_STRING([--with-oss], [Enable support for OSS @<:@autodetect@:>@]), [with_oss=$withval]) @@ -149,10 +153,17 @@ if test "x$with_oss" != "xno"; then AC_CHECK_LIB(ossaudio, _oss_ioctl, have_libossaudio=yes, have_libossaudio=no) fi fi +if [[ "x$with_jack" != "xno" ] || [ "x$with_pulseaudio" != "xno" ]]; then + PKG_PROG_PKG_CONFIG +fi have_jack=no if test "x$with_jack" != "xno"; then PKG_CHECK_MODULES(JACK, jack, have_jack=yes, have_jack=no) fi +have_pulse=no +if test "x$with_pulseaudio" != "xno"; then + PKG_CHECK_MODULES(PULSE, libpulse, have_pulse=yes, have_pulse=no) +fi dnl sizeof checks: we will need a 16-bit and a 32-bit type @@ -386,11 +397,26 @@ case "${host_os}" in if [[ "$have_jack" = "yes" ] && [ "$with_jack" != "no" ]] ; then DLL_LIBS="$DLL_LIBS $JACK_LIBS" CFLAGS="$CFLAGS $JACK_CFLAGS" - OTHER_OBJS="$OTHER_OBJS src/hostapi/jack/pa_jack.o src/common/pa_ringbuffer.o" + OTHER_OBJS="$OTHER_OBJS src/hostapi/jack/pa_jack.o" INCLUDES="$INCLUDES pa_jack.h" AC_DEFINE(PA_USE_JACK,1) fi + if [[ "$have_pulse" = "yes" ] || [ "$have_jack" = "yes" ]] ; then + OTHER_OBJS="$OTHER_OBJS src/common/pa_ringbuffer.o" + fi + + if [[ "$have_pulse" = "yes" ] && [ "$with_pulse" != "no" ]] ; then + INCLUDES="$INCLUDES pa_linux_pulseaudio.h" + DLL_LIBS="$DLL_LIBS $PULSE_LIBS" + CFLAGS="$CFLAGS $PULSE_CFLAGS" + OTHER_OBJS="$OTHER_OBJS src/hostapi/pulseaudio/pa_linux_pulseaudio_cb.o" + OTHER_OBJS="$OTHER_OBJS src/hostapi/pulseaudio/pa_linux_pulseaudio_block.o" + OTHER_OBJS="$OTHER_OBJS src/hostapi/pulseaudio/pa_linux_pulseaudio.o" + dnl INCLUDES="$INCLUDES pa_pulseaudio.h src/hostapi/pulseaudio/pa_linux_pulseaudio_internal.h src/hostapi/pulseaudio/pa_linux_pulseaudio_block_internal.h src/hostapi/pulseaudio/pa_linux_pulseaudio_cb_internal.h" + AC_DEFINE(PA_USE_PULSEAUDIO,1) + fi + if [[ "$with_oss" != "no" ]] ; then OTHER_OBJS="$OTHER_OBJS src/hostapi/oss/pa_unix_oss.o" if [[ "$have_libossaudio" = "yes" ]] ; then @@ -489,6 +515,7 @@ case "$target_os" in AudioIO ..................... $have_audioio OSS ......................... $have_oss JACK ........................ $have_jack + PulseAudio .................. $have_pulse ]) ;; esac diff --git a/3rdparty/portaudio/doc/src/tutorial/compile_windows_asio_msvc.dox b/3rdparty/portaudio/doc/src/tutorial/compile_windows_asio_msvc.dox index 858071fa510ca..eeae8221cb858 100644 --- a/3rdparty/portaudio/doc/src/tutorial/compile_windows_asio_msvc.dox +++ b/3rdparty/portaudio/doc/src/tutorial/compile_windows_asio_msvc.dox @@ -56,6 +56,7 @@ pa_stream.c (portaudio\src\common) pa_trace.c (portaudio\src\common) pa_win_hostapis.c (portaudio\src\os\win) pa_win_util.c (portaudio\src\os\win) +pa_win_version.c (portaudio\src\os\win) pa_win_coinitialize.c (portaudio\src\os\win) pa_win_waveformat.c (portaudio\src\os\win) pa_x86_plain_converters.c (portaudio\src\os\win) diff --git a/3rdparty/portaudio/include/pa_linux_pulseaudio.h b/3rdparty/portaudio/include/pa_linux_pulseaudio.h new file mode 100644 index 0000000000000..7c5dfce05fd8e --- /dev/null +++ b/3rdparty/portaudio/include/pa_linux_pulseaudio.h @@ -0,0 +1,79 @@ +#ifndef PA_LINUX_PULSEAUDIO_H +#define PA_LINUX_PULSEAUDIO_H + +/* + * $Id$ + * PortAudio Portable Real-Time Audio Library + * PulseAudio-specific extensions + * + * Copyright (c) 1999-2000 Ross Bencina and Phil Burk + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/* + * The text above constitutes the entire PortAudio license; however, + * the PortAudio community also makes the following non-binding requests: + * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. It is also + * requested that these non-binding requests be included along with the + * license above. + */ + +/** @file + * @ingroup public_header + * @brief PulseAudio-specific PortAudio API extension header file. + */ + +#include "portaudio.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Renames the PulseAudio description for the source that is opened + * by PortAudio. + * + * @param s The PortAudio stream to operate on. + * @param streamName The new name/description of the source. + * + * @return paNoError on success or the error encountered otherwise. + */ +PaError PaPulseAudio_RenameSource( PaStream *s, const char *streamName ); + +/** + * Renames the PulseAudio description for the sink that is opened + * by PortAudio. + * + * @param s The PortAudio stream to operate on. + * @param streamName The new name/description of the sink. + * + * @return paNoError on success or the error encountered otherwise. + */ +PaError PaPulseAudio_RenameSink( PaStream *s, const char *streamName ); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/3rdparty/portaudio/include/pa_win_wasapi.h b/3rdparty/portaudio/include/pa_win_wasapi.h index 456bfa967b431..ca75acb8b333f 100644 --- a/3rdparty/portaudio/include/pa_win_wasapi.h +++ b/3rdparty/portaudio/include/pa_win_wasapi.h @@ -82,7 +82,12 @@ typedef enum PaWasapiFlags playback formats that do not match the current configured system settings. this is in particular required for streams not matching the system mixer sample rate. only applies in Shared mode. */ - paWinWasapiAutoConvert = (1 << 6) + paWinWasapiAutoConvert = (1 << 6), + + /* use Passthrough mode for sending encoded audio data in PCM containers to the audio device, + refer to Microsoft documentation "Representing Formats for IEC 61937 Transmissions" for more + details about data representation and stream configuration */ + paWinWasapiPassthrough = (1 << 7), } PaWasapiFlags; #define paWinWasapiExclusive (paWinWasapiExclusive) @@ -92,6 +97,7 @@ PaWasapiFlags; #define paWinWasapiThreadPriority (paWinWasapiThreadPriority) #define paWinWasapiExplicitSampleFormat (paWinWasapiExplicitSampleFormat) #define paWinWasapiAutoConvert (paWinWasapiAutoConvert) +#define paWinWasapiPassthrough (paWinWasapiPassthrough) /* Stream state. @@ -302,6 +308,61 @@ typedef enum PaWasapiStreamOption PaWasapiStreamOption; +/** Passthrough format. + + Format ids are obtained from the Microsoft documentation "Representing Formats for IEC 61937 Transmissions" + and are composed by such formula where GUID is the guid of passthrough format: + GUID.Data1 << 16 | GUID.Data2. + + @see PaWasapiStreamPassthrough + @version Available as of 19.8.0 +*/ +typedef enum PaWasapiPassthroughFormat +{ + ePassthroughFormatPcmIec60958 = 0x00000000, + ePassthroughFormatDolbyDigital = 0x00920000, + ePassthroughFormatMpeg1 = 0x00030cea, + ePassthroughFormatMpeg3 = 0x00040cea, + ePassthroughFormatMpeg2 = 0x00050cea, + ePassthroughFormatAac = 0x00060cea, + ePassthroughFormatDts = 0x00080cea, + ePassthroughFormatDolbyDigitalPlus = 0x000a0cea, + ePassthroughFormatDolbyDigitalPlusAtmos = 0x010a0cea, + ePassthroughFormatDtsHd = 0x000b0cea, + ePassthroughFormatDtsXE1 = 0x010b0cea, + ePassthroughFormatDtsXE2 = 0x030b0cea, + ePassthroughFormatDolbyMlp = 0x000c0cea, + ePassthroughFormatDolbyMat20 = 0x010c0cea, + ePassthroughFormatDolbyMat21 = 0x030c0cea, + ePassthroughFormatWmaPro = 0x01640000, + ePassthroughFormatAtrac = 0x00080cea, + ePassthroughFormatOneBitAudio = 0x00090cea, + ePassthroughFormatDst = 0x000d0cea, +} +PaWasapiPassthroughFormat; + + +/** Passthrough details. + + Passthrough details provide direct link to the additional members in WAVEFORMATEXTENSIBLE_IEC61937. + Passthrough mode allows to pass encoded data inside the PCM containers to the audio device. + + Detailed description about supported formats and examples are provided in Microsoft documentation + "Representing Formats for IEC 61937 Transmissions". + + @see paWinWasapiPassthrough + @version Available as of 19.8.0 +*/ +typedef struct PaWasapiStreamPassthrough +{ + PaWasapiPassthroughFormat formatId; + unsigned int encodedSamplesPerSec; + unsigned int encodedChannelCount; + unsigned int averageBytesPerSec; +} +PaWasapiStreamPassthrough; + + /* Stream descriptor. */ typedef struct PaWasapiStreamInfo { @@ -347,6 +408,13 @@ typedef struct PaWasapiStreamInfo @version Available as of 19.6.0 */ PaWasapiStreamOption streamOption; + + /** Passthrough details. + @note paWinWasapiPassthrough flag must be specified in PaWasapiStreamInfo::flags to enable Passthrough mode. + @see paWinWasapiPassthrough + @version Available as of 19.7.0 + */ + PaWasapiStreamPassthrough passthrough; } PaWasapiStreamInfo; diff --git a/3rdparty/portaudio/include/portaudio.h b/3rdparty/portaudio/include/portaudio.h index 8a8250fa258b2..ab1efeb55921c 100644 --- a/3rdparty/portaudio/include/portaudio.h +++ b/3rdparty/portaudio/include/portaudio.h @@ -151,7 +151,8 @@ typedef enum PaErrorCode paCanNotReadFromAnOutputOnlyStream, paCanNotWriteToAnInputOnlyStream, paIncompatibleStreamHostApi, - paBadBufferPtr + paBadBufferPtr, + paCanNotInitializeRecursively } PaErrorCode; @@ -288,7 +289,8 @@ typedef enum PaHostApiTypeId paJACK=12, paWASAPI=13, paAudioScienceHPI=14, - paAudioIO=15 + paAudioIO=15, + paPulseAudio=16 } PaHostApiTypeId; diff --git a/3rdparty/portaudio/msvc/portaudio.dsp b/3rdparty/portaudio/msvc/portaudio.dsp index bfe1437cd9d8c..0b81ef61f3988 100644 --- a/3rdparty/portaudio/msvc/portaudio.dsp +++ b/3rdparty/portaudio/msvc/portaudio.dsp @@ -240,6 +240,10 @@ SOURCE=..\..\src\os\win\pa_win_util.c # End Source File # Begin Source File +SOURCE=..\..\src\os\win\pa_win_version.c +# End Source File +# Begin Source File + SOURCE=..\..\src\os\win\pa_win_waveformat.c # End Source File # Begin Source File diff --git a/3rdparty/portaudio/msvc/portaudio.vcproj b/3rdparty/portaudio/msvc/portaudio.vcproj index 0eb6321dfa120..943dc4e80c15f 100644 --- a/3rdparty/portaudio/msvc/portaudio.vcproj +++ b/3rdparty/portaudio/msvc/portaudio.vcproj @@ -1819,6 +1819,10 @@ /> + + diff --git a/3rdparty/portaudio/qa/loopback/src/paqa.c b/3rdparty/portaudio/qa/loopback/src/paqa.c index a3ed4acfb5580..909916b13881e 100644 --- a/3rdparty/portaudio/qa/loopback/src/paqa.c +++ b/3rdparty/portaudio/qa/loopback/src/paqa.c @@ -120,8 +120,8 @@ typedef struct LoopbackContext_s volatile int minInputOutputDelta; volatile int maxInputOutputDelta; - int minFramesPerBuffer; - int maxFramesPerBuffer; + unsigned long minFramesPerBuffer; + unsigned long maxFramesPerBuffer; int primingCount; TestParameters *test; volatile int done; @@ -849,7 +849,7 @@ static int PaQa_SingleLoopBackTest( UserOptions *userOptions, TestParameters *te { double latencyMSec; - printf( "%4d-%4d | ", + printf( "%4lu-%4lu | ", loopbackContext.minFramesPerBuffer, loopbackContext.maxFramesPerBuffer ); @@ -1351,7 +1351,8 @@ int TestSampleFormatConversion( void ) const char charInput[] = { 127, 64, -64, -128 }; const unsigned char ucharInput[] = { 255, 128+64, 64, 0 }; const short shortInput[] = { 32767, 32768/2, -32768/2, -32768 }; - const int intInput[] = { 2147483647, 2147483647/2, -1073741824 /*-2147483648/2 doesn't work in msvc*/, -2147483648 }; + const int int_minus_2147483648 = (-2147483647 - 1); /*"-2147483648" as a signed integer. See PR #814*/ + const int intInput[] = { 2147483647, 2147483647/2, int_minus_2147483648/2, int_minus_2147483648 }; float floatOutput[4]; short shortOutput[4]; diff --git a/3rdparty/portaudio/qa/paqa_devs.c b/3rdparty/portaudio/qa/paqa_devs.c index 81b7509c736e0..1a70e842d896d 100644 --- a/3rdparty/portaudio/qa/paqa_devs.c +++ b/3rdparty/portaudio/qa/paqa_devs.c @@ -52,108 +52,205 @@ * license above. */ +#include #include -#include +#include /* for EXIT_SUCCESS and EXIT_FAILURE */ #include #define _USE_MATH_DEFINES #include #include "portaudio.h" #include "pa_trace.h" +#include "paqa_macros.h" /****************************************** Definitions ***********/ +#define RUN_TIME_SECONDS (1.2) +#define BYPASS_TESTS (0) /* If 1 then skip actual tests and just iterate. */ + #define MODE_INPUT (0) #define MODE_OUTPUT (1) #define MAX_TEST_CHANNELS (4) #define LOWEST_FREQUENCY (300.0) -#define LOWEST_SAMPLE_RATE (8000.0) -#define PHASE_INCREMENT (2.0 * M_PI * LOWEST_FREQUENCY / LOWEST_SAMPLE_RATE) #define SINE_AMPLITUDE (0.2) +#define MILLIS_PER_SECOND (1000.0) +#define DEFAULT_FRAMES_PER_BUFFER (128) -typedef struct PaQaData +#define TEST_LEVEL_QUICK (0) +#define TEST_LEVEL_NORMAL (1) +#define TEST_LEVEL_EXHAUSTIVE (2) + +PAQA_INSTANTIATE_GLOBALS + +typedef struct PaSineOscillator { - unsigned long framesLeft; - int numChannels; - int bytesPerSample; - int mode; float phase; - PaSampleFormat format; -} -PaQaData; + float phaseIncrement; +} PaSineOscillator; + +/* Parameters that cover all options for a test. + */ +typedef struct PaQaTestParameters +{ + PaDeviceIndex deviceID; + PaSampleFormat format; + double sampleRate; + double durationSeconds; + double suggestedLatency; + int framesPerBuffer; + int numInputChannels; + int numOutputChannels; + int mode; + int useCallback; + int useNonInterleaved; /* Test paNonInterleaved flag */ +} PaQaTestParameters; + +PaQaTestParameters kDefaultTestParameters = { + 0, /* deviceId */ + paFloat32, + 44100, + RUN_TIME_SECONDS, + 0.020, + DEFAULT_FRAMES_PER_BUFFER, + 0, /* numInputChannels */ + 1, /* numOutputChannels */ + MODE_OUTPUT, + 1, /* useCallback */ + 0, /* useNonInterleaved */ +}; + +/* Runtime data used during the test. */ +typedef struct PaQaData +{ + const PaQaTestParameters *parameters; + // Dynamic state. + int bytesPerSample; + volatile unsigned long frameCounter; + volatile unsigned long framesLeft; + unsigned long framesPerBurst; + unsigned long minFramesPerBuffer; + unsigned long maxFramesPerBuffer; + unsigned long framesDuration; + PaSineOscillator sineOscillators[MAX_TEST_CHANNELS]; + void *audioBuffer; +} PaQaData; /****************************************** Prototypes ***********/ -static void TestDevices( int mode, int allDevices ); -static void TestFormats( int mode, PaDeviceIndex deviceID, double sampleRate, - int numChannels ); -static int TestAdvance( int mode, PaDeviceIndex deviceID, double sampleRate, - int numChannels, PaSampleFormat format ); +static int TestSingleStreamParameters(PaQaTestParameters parameters); static int QaCallback( const void *inputBuffer, void *outputBuffer, unsigned long framesPerBuffer, const PaStreamCallbackTimeInfo* timeInfo, PaStreamCallbackFlags statusFlags, void *userData ); -/****************************************** Globals ***********/ -static int gNumPassed = 0; -static int gNumFailed = 0; - -/****************************************** Macros ***********/ -/* Print ERROR if it fails. Tally success or failure. */ -/* Odd do-while wrapper seems to be needed for some compilers. */ -#define EXPECT(_exp) \ - do \ - { \ - if ((_exp)) {\ - /* printf("SUCCESS for %s\n", #_exp ); */ \ - gNumPassed++; \ - } \ - else { \ - printf("ERROR - 0x%x - %s for %s\n", result, \ - ((result == 0) ? "-" : Pa_GetErrorText(result)), \ - #_exp ); \ - gNumFailed++; \ - goto error; \ - } \ - } while(0) - -static float NextSineSample( PaQaData *data ) +static void PaQaSetupData(PaQaData *myData, + const PaQaTestParameters *parameters) +{ + memset(myData, 0, sizeof(PaQaData)); + + myData->parameters = parameters; + myData->frameCounter = 0; + myData->framesLeft = (unsigned long) (parameters->sampleRate * parameters->durationSeconds); + + myData->minFramesPerBuffer = UINT32_MAX; + myData->maxFramesPerBuffer = 0; + + for (int channelIndex = 0; channelIndex < MAX_TEST_CHANNELS; channelIndex++) + { + myData->sineOscillators[channelIndex].phase = 0.0f; + myData->sineOscillators[channelIndex].phaseIncrement = + (2.0 * M_PI * LOWEST_FREQUENCY / parameters->sampleRate); + } + + switch( parameters->format ) + { + case paFloat32: + case paInt32: + case paInt24: + myData->bytesPerSample = 4; + break; + /* case paPackedInt24: + myData->bytesPerSample = 3; + break; */ + default: + myData->bytesPerSample = 2; + break; + } + myData->framesPerBurst = (parameters->framesPerBuffer == 0) ? 128 : parameters->framesPerBuffer; + if (parameters->useCallback == 0) { + /* We need our own buffer for blocking IO. */ + int numChannels = (parameters->mode == MODE_OUTPUT) + ? parameters->numOutputChannels + : parameters->numInputChannels; + myData->audioBuffer = malloc(myData->bytesPerSample * numChannels * myData->framesPerBurst); + } +} + +static void PaQaTeardownData(PaQaData *myData, + const PaQaTestParameters *parameters) +{ + (void) parameters; + free(myData->audioBuffer); +} + +static float NextSineSample( PaSineOscillator *sineOscillator ) { - float phase = data->phase + PHASE_INCREMENT; - if( phase > M_PI ) phase -= (float) (2.0 * M_PI); - data->phase = phase; + float phase = sineOscillator->phase + sineOscillator->phaseIncrement; + if( phase > (float)M_PI ) phase -= (float)(2.0 * M_PI); + sineOscillator->phase = phase; return sinf(phase) * SINE_AMPLITUDE; } +#define SETUP_BUFFERS(_data_type) \ + _data_type *out; \ + int stride; \ + if (parameters->useNonInterleaved) { \ + /* outputData points to an array of pointers to the buffers. */ \ + void **buffers = (void **)outputData; \ + out = (_data_type *)buffers[channelIndex]; \ + stride = 1; \ + } else { \ + out = &((_data_type *) outputData)[channelIndex]; \ + stride = parameters->numOutputChannels; \ + } + /*******************************************************************/ /* This routine will be called by the PortAudio engine when audio is needed. ** It may be called at interrupt level on some machines so don't do anything ** that could mess up the system like calling malloc() or free(). */ -static int QaCallback( const void *inputBuffer, void *outputBuffer, +static int QaCallback( const void *inputData, + void *outputData, unsigned long framesPerBuffer, const PaStreamCallbackTimeInfo* timeInfo, PaStreamCallbackFlags statusFlags, void *userData ) { unsigned long frameIndex; - unsigned long channelIndex; + int channelIndex; float sample; PaQaData *data = (PaQaData *) userData; - (void) inputBuffer; + const PaQaTestParameters *parameters = data->parameters; + (void) inputData; + + data->minFramesPerBuffer = (framesPerBuffer < data->minFramesPerBuffer) + ? framesPerBuffer : data->minFramesPerBuffer; + data->maxFramesPerBuffer = (framesPerBuffer > data->maxFramesPerBuffer) + ? framesPerBuffer : data->maxFramesPerBuffer; /* Play simple sine wave. */ - if( data->mode == MODE_OUTPUT ) + if( parameters->mode == MODE_OUTPUT ) { - switch( data->format ) + switch( parameters->format ) { case paFloat32: { - float *out = (float *) outputBuffer; - for( frameIndex = 0; frameIndex < framesPerBuffer; frameIndex++ ) + for( channelIndex = 0; channelIndex < parameters->numOutputChannels; channelIndex++ ) { - sample = NextSineSample( data ); - for( channelIndex = 0; channelIndex < data->numChannels; channelIndex++ ) + SETUP_BUFFERS(float); + for( frameIndex = 0; frameIndex < framesPerBuffer; frameIndex++ ) { - *out++ = sample; + sample = NextSineSample( &data->sineOscillators[channelIndex] ); + *out = sample; + out += stride; } } } @@ -161,13 +258,14 @@ static int QaCallback( const void *inputBuffer, void *outputBuffer, case paInt32: { - int *out = (int *) outputBuffer; - for( frameIndex = 0; frameIndex < framesPerBuffer; frameIndex++ ) + for( channelIndex = 0; channelIndex < parameters->numOutputChannels; channelIndex++ ) { - sample = NextSineSample( data ); - for( channelIndex = 0; channelIndex < data->numChannels; channelIndex++ ) + SETUP_BUFFERS(int32_t); + for( frameIndex = 0; frameIndex < framesPerBuffer; frameIndex++ ) { - *out++ = ((int)(sample * 0x00800000)) << 8; + sample = NextSineSample( &data->sineOscillators[channelIndex] ); + *out = ((int32_t)(sample * 8388607)) << 8; + out += stride; } } } @@ -175,13 +273,14 @@ static int QaCallback( const void *inputBuffer, void *outputBuffer, case paInt16: { - short *out = (short *) outputBuffer; - for( frameIndex = 0; frameIndex < framesPerBuffer; frameIndex++ ) + for( channelIndex = 0; channelIndex < parameters->numOutputChannels; channelIndex++ ) { - sample = NextSineSample( data ); - for( channelIndex = 0; channelIndex < data->numChannels; channelIndex++ ) + SETUP_BUFFERS(int16_t); + for( frameIndex = 0; frameIndex < framesPerBuffer; frameIndex++ ) { - *out++ = (short)(sample * 32767); + sample = NextSineSample( &data->sineOscillators[channelIndex] ); + *out = (int16_t)(sample * 32767); + out += stride; } } } @@ -189,123 +288,418 @@ static int QaCallback( const void *inputBuffer, void *outputBuffer, default: { - unsigned char *out = (unsigned char *) outputBuffer; - unsigned long numBytes = framesPerBuffer * data->numChannels * data->bytesPerSample; + unsigned char *out = (unsigned char *) outputData; + unsigned long numBytes = framesPerBuffer * parameters->numOutputChannels * data->bytesPerSample; memset(out, 0, numBytes); } break; } } + + data->frameCounter += framesPerBuffer; + /* Are we through yet? */ if( data->framesLeft > framesPerBuffer ) { PaUtil_AddTraceMessage("QaCallback: running. framesLeft", data->framesLeft ); data->framesLeft -= framesPerBuffer; - return 0; + return paContinue; } else { PaUtil_AddTraceMessage("QaCallback: DONE! framesLeft", data->framesLeft ); data->framesLeft = 0; - return 1; + return paComplete; } } -/*******************************************************************/ -static void usage( const char *name ) -{ - printf("%s [-a]\n", name); - printf(" -a - Test ALL devices, otherwise just the default devices.\n"); - printf(" -i - Test INPUT only.\n"); - printf(" -o - Test OUTPUT only.\n"); - printf(" -? - Help\n"); +static PaError CheckBlockingIO(PaStream *stream, + PaQaData *data, + int millis) { + PaError result = paNoError; + double elapsedTime = 0.0; + double millisPerBurst = MILLIS_PER_SECOND * data->framesPerBurst / data->parameters->sampleRate; + while (elapsedTime < millis) { + int callbackResult; + if (data->parameters->mode == MODE_OUTPUT) { + callbackResult = QaCallback(NULL /*inputBuffer */, + data->audioBuffer, + data->framesPerBurst, + NULL /* timeInfo */, // TODO + 0, // stream flags + data); + if (callbackResult == 0) { + result = Pa_WriteStream(stream, data->audioBuffer, data->framesPerBurst); + ASSERT_EQ(paNoError, result); + } + } else if (data->parameters->mode == MODE_INPUT) { + result = Pa_ReadStream(stream, data->audioBuffer, data->framesPerBurst); + ASSERT_EQ(paNoError, result); + callbackResult = QaCallback(data->audioBuffer, + NULL /*outputBuffer */, + data->framesPerBurst, + NULL /* timeInfo */, // TODO + 0, // stream flags + data); + } + elapsedTime += millisPerBurst; + } +error: + return result; +} + +static void CheckDefaultCallbackRun(PaStream *stream, + PaQaData *data) { + PaError result = paNoError; + PaTime oldStreamTimeMillis = 0.0; + PaTime startStreamTimeMillis = 0.0; + unsigned long oldFramesLeft = INT32_MAX; + + oldStreamTimeMillis = Pa_GetStreamTime(stream) * MILLIS_PER_SECOND; + + ASSERT_EQ(0, Pa_IsStreamActive(stream)); + ASSERT_EQ(1, Pa_IsStreamStopped(stream)); + + ASSERT_EQ(paNoError, result = Pa_StartStream( stream )); + startStreamTimeMillis = Pa_GetStreamTime(stream) * MILLIS_PER_SECOND; + + ASSERT_EQ(1, Pa_IsStreamActive(stream)); + ASSERT_EQ(0, Pa_IsStreamStopped(stream)); + + /* Sleep long enough for the stream callback to have stopped itself. */ + while ((oldStreamTimeMillis - startStreamTimeMillis) < ((RUN_TIME_SECONDS + 0.5) * MILLIS_PER_SECOND) + && (data->framesLeft > 0)) + { + if (data->parameters->useCallback) { + Pa_Sleep(200); + } else { + result = CheckBlockingIO(stream, + data, + 200); + ASSERT_EQ(paNoError, result); + } + + PaTime newStreamTime = Pa_GetStreamTime(stream) * MILLIS_PER_SECOND; + //printf("oldStreamTime = %9.6f, newStreamTime = %9.6f\n", oldStreamTime, newStreamTime ); /**/ + ASSERT_LE(oldStreamTimeMillis, newStreamTime); + + /* Check to make sure callback is decrementing framesLeft. */ + unsigned long newFramesLeft = data->framesLeft; + //printf("oldFrames = %lu, newFrames = %lu\n", oldFramesLeft, newFramesLeft ); + ASSERT_GE(oldFramesLeft, newFramesLeft); + + oldStreamTimeMillis = newStreamTime; + oldFramesLeft = newFramesLeft; + } + + ASSERT_EQ(0, data->framesLeft); + ASSERT_LE((1 * data->parameters->sampleRate), data->frameCounter); + + if (data->parameters->framesPerBuffer > 0) { + ASSERT_EQ(data->parameters->framesPerBuffer, data->minFramesPerBuffer); + ASSERT_EQ(data->parameters->framesPerBuffer, data->maxFramesPerBuffer); + } else { + ASSERT_GT(data->minFramesPerBuffer, 0); + ASSERT_LT(data->maxFramesPerBuffer, data->parameters->sampleRate); + } + + ASSERT_EQ(data->parameters->useCallback ? 0 : 1, Pa_IsStreamActive(stream)); + ASSERT_EQ(0, Pa_IsStreamStopped(stream)); + + ASSERT_EQ(paNoError, result = Pa_StopStream( stream )); + + ASSERT_EQ(0, Pa_IsStreamActive(stream)); + ASSERT_EQ(1, Pa_IsStreamStopped(stream)); + + ASSERT_EQ(paNoError, result = Pa_CloseStream( stream )); + return; + +error: + printf("result = %d = for %s\n", result, Pa_GetErrorText(result)); + Pa_CloseStream(stream); + return; } /*******************************************************************/ -int main( int argc, char **argv ); -int main( int argc, char **argv ) +static int TestSingleStreamParameters(PaQaTestParameters testParameters) { - int i; - PaError result; - int allDevices = 0; - int testOutput = 1; - int testInput = 1; - char *executableName = argv[0]; + PaStreamParameters inputParameters, outputParameters, *ipp, *opp; + PaStream *stream = NULL; + PaQaData myData; + int numChannels = 0; - /* Parse command line parameters. */ - i = 1; - while( isampleRate)); + if (testParameters.mode == MODE_INPUT) { + ASSERT_EQ(0, (int)(streamInfo->outputLatency * 1000)); + } else { + ASSERT_EQ(0, (int)(streamInfo->inputLatency * 1000)); + } + } + CheckDefaultCallbackRun(stream, &myData); - default: - printf("Illegal option: %s\n", arg); - case '?': - usage( executableName ); - exit(1); - break; + } else { + printf(" Parameters NOT supported.\n"); + } + PaQaTeardownData(&myData, &testParameters); + return 0; + +error: + if( stream != NULL ) Pa_CloseStream( stream ); + PaQaTeardownData(&myData, &testParameters); + return -1; +} + + +static void RunQuickTest() +{ + PaQaTestParameters parameters = kDefaultTestParameters; + +#if 1 + printf("\n=========== INPUT ==============\n"); + parameters.mode = MODE_INPUT; + parameters.deviceID = Pa_GetDefaultInputDevice(); + parameters.format = paFloat32; + + parameters.sampleRate = 44100; + parameters.numInputChannels = 1; + TestSingleStreamParameters(parameters); + parameters.sampleRate = 22050; + TestSingleStreamParameters(parameters); + + parameters.sampleRate = 44100; + parameters.numInputChannels = 2; + TestSingleStreamParameters(parameters); + + parameters.useCallback = 0; + TestSingleStreamParameters(parameters); /* Blocking */ + parameters.useNonInterleaved = 1; + TestSingleStreamParameters(parameters); /* Blocking, NonInterleaved */ + parameters.useCallback = 1; + TestSingleStreamParameters(parameters); /* NonInterleaved */ + parameters.useCallback = 1; +#endif + + printf("\n=========== OUTPUT =============\n"); + parameters = kDefaultTestParameters; + parameters.mode = MODE_OUTPUT; + parameters.deviceID = Pa_GetDefaultOutputDevice(); + parameters.sampleRate = 48000; + parameters.numOutputChannels = 1; + parameters.format = paFloat32; + parameters.useCallback = 0; + TestSingleStreamParameters(parameters); + + /* Interleaved */ + parameters = kDefaultTestParameters; + parameters.deviceID = Pa_GetDefaultOutputDevice(); + parameters.useNonInterleaved = 0; + parameters.numOutputChannels = 1; + parameters.useCallback = 1; + parameters.format = paFloat32; + TestSingleStreamParameters(parameters); + parameters.useCallback = 0; + parameters.format = paFloat32; + TestSingleStreamParameters(parameters); /* Blocking */ + parameters.useCallback = 1; + + parameters.sampleRate = 44100; + parameters.numOutputChannels = 2; + parameters.format = paFloat32; + TestSingleStreamParameters(parameters); + + parameters.sampleRate = 22050; + parameters.numOutputChannels = 2; + parameters.format = paInt16; + TestSingleStreamParameters(parameters); + + /* Non-Interleaved */ + parameters = kDefaultTestParameters; + parameters.deviceID = Pa_GetDefaultOutputDevice(); + parameters.useNonInterleaved = 1; + parameters.numOutputChannels = 2; + parameters.format = paFloat32; + parameters.useCallback = 0; + TestSingleStreamParameters(parameters); /* Blocking */ + parameters.useCallback = 1; + TestSingleStreamParameters(parameters); /* Blocking */ + parameters.format = paInt16; + TestSingleStreamParameters(parameters); + parameters.format = paInt32; + TestSingleStreamParameters(parameters); +} + +static const double constStandardSampleRates_[] = { + 8000.0, 9600.0, 11025.0, 12000.0, 16000.0, 22050.0, + 24000.0, 32000.0, 44100.0, 48000.0, 88200.0, 96000.0, + -1.0 }; /* Negative terminated list. */ + +static const PaSampleFormat constFormatsToTest_[] = { + paFloat32, paInt32, paInt16, 0 +}; /* Zero terminated list. */ + +/** + * Iterate through each option with other options set to default. + */ +static void TestNormal( int mode, int allDevices ) +{ + PaQaTestParameters parameters = kDefaultTestParameters; + int id, jc, i; + int maxChannels; + int isDefault; + const PaDeviceInfo *pdi; + int numDevices = Pa_GetDeviceCount(); + parameters.mode = mode; + + for( id=0; idmaxInputChannels; + isDefault = ( id == Pa_GetDefaultInputDevice()); + } else { + maxChannels = pdi->maxOutputChannels; + isDefault = ( id == Pa_GetDefaultOutputDevice()); + } + if( maxChannels > MAX_TEST_CHANNELS ) + maxChannels = MAX_TEST_CHANNELS; + if (maxChannels == 0) continue; // skip this device, wrong direction + + if (!allDevices && !isDefault) continue; // skip this device + + printf("\n===========================================================\n"); + printf(" Device = %s\n", pdi->name ); + printf("===========================================================\n"); + for( jc=1; jc<=maxChannels; jc++ ) + { + if (mode == MODE_INPUT) { + parameters.numInputChannels = jc; + } else { + parameters.numOutputChannels = jc; } + TestSingleStreamParameters(parameters); } - else + + /* Try each standard sample rate. */ + for( i=0; constStandardSampleRates_[i] > 0; i++ ) { - printf("Illegal argument: %s\n", arg); - usage( executableName ); - return 1; + parameters.sampleRate = constStandardSampleRates_[i]; + TestSingleStreamParameters(parameters); + } + parameters.sampleRate = pdi->defaultSampleRate; + if (mode == MODE_INPUT) { + parameters.suggestedLatency = pdi->defaultHighInputLatency; + TestSingleStreamParameters(parameters); + parameters.suggestedLatency = pdi->defaultLowInputLatency; + TestSingleStreamParameters(parameters); + } else { + parameters.suggestedLatency = pdi->defaultHighOutputLatency; + TestSingleStreamParameters(parameters); + parameters.suggestedLatency = pdi->defaultLowOutputLatency; + TestSingleStreamParameters(parameters); } - i += 1; - } - EXPECT(sizeof(short) == 2); /* The callback assumes we have 16-bit shorts. */ - EXPECT(sizeof(int) == 4); /* The callback assumes we have 32-bit ints. */ - EXPECT( ((result=Pa_Initialize()) == 0) ); + for (int callback = 0; callback < 2; callback++) { + parameters.useCallback = callback; + for (int nonInterleaved = 0; nonInterleaved < 2; nonInterleaved++) { + parameters.useNonInterleaved = nonInterleaved; + TestSingleStreamParameters(parameters); + } + } + parameters.useCallback = 1; + parameters.useNonInterleaved = 0; - if( testOutput ) - { - printf("\n---- Test OUTPUT ---------------\n"); - TestDevices( MODE_OUTPUT, allDevices ); - } - if( testInput ) - { - printf("\n---- Test INPUT ---------------\n"); - TestDevices( MODE_INPUT, allDevices ); + for (int jf = 0; constFormatsToTest_[jf] > 0; jf++) { + parameters.format = constFormatsToTest_[jf]; + TestSingleStreamParameters(parameters); + } } - -error: - Pa_Terminate(); - printf("QA Report: %d passed, %d failed.\n", gNumPassed, gNumFailed ); - return (gNumFailed > 0) ? 1 : 0; } /******************************************************************* -* Try each output device, through its full range of capabilities. */ -static void TestDevices( int mode, int allDevices ) +* Test each output device, through its full range of capabilities. */ +static void TestExhaustive( int mode, int allDevices ) { + PaQaTestParameters parameters = kDefaultTestParameters; int id, jc, i; int maxChannels; int isDefault; const PaDeviceInfo *pdi; - static double standardSampleRates[] = { 8000.0, 9600.0, 11025.0, 12000.0, - 16000.0, 22050.0, 24000.0, - 32000.0, 44100.0, 48000.0, - 88200.0, 96000.0, - -1.0 }; /* Negative terminated list. */ int numDevices = Pa_GetDeviceCount(); + parameters.mode = mode; + for( id=0; id MAX_TEST_CHANNELS ) maxChannels = MAX_TEST_CHANNELS; + if (maxChannels == 0) continue; // skip this device, wrong direction if (!allDevices && !isDefault) continue; // skip this device + printf("\n===========================================================\n"); + printf(" Device = %s\n", pdi->name ); + printf("===========================================================\n"); for( jc=1; jc<=maxChannels; jc++ ) { - printf("\n===========================================================\n"); - printf(" Device = %s\n", pdi->name ); - printf("===========================================================\n"); + printf("\n---------------------- NumChannels = %d ------------\n", jc ); + if (mode == MODE_INPUT) { + parameters.numInputChannels = jc; + } else { + parameters.numOutputChannels = jc; + } /* Try each standard sample rate. */ - for( i=0; standardSampleRates[i] > 0; i++ ) + for( i=0; constStandardSampleRates_[i] > 0; i++ ) { - TestFormats( mode, (PaDeviceIndex)id, standardSampleRates[i], jc ); + parameters.sampleRate = constStandardSampleRates_[i]; + for (int callback = 0; callback < 2; callback++) { + parameters.useCallback = callback; + for (int nonInterleaved = 0; nonInterleaved < 2; nonInterleaved++) { + parameters.useNonInterleaved = nonInterleaved; + for (int jf = 0; constFormatsToTest_[jf] > 0; jf++) { + parameters.format = constFormatsToTest_[jf]; + TestSingleStreamParameters(parameters); + } + } + } } } } } /*******************************************************************/ -static void TestFormats( int mode, PaDeviceIndex deviceID, double sampleRate, - int numChannels ) +static void usage( const char *name ) { - TestAdvance( mode, deviceID, sampleRate, numChannels, paFloat32 ); - TestAdvance( mode, deviceID, sampleRate, numChannels, paInt16 ); - TestAdvance( mode, deviceID, sampleRate, numChannels, paInt32 ); - /* TestAdvance( mode, deviceID, sampleRate, numChannels, paInt24 ); */ + printf("%s [-a] {-tN}\n", name); + printf(" -a - Test ALL devices, otherwise just the default devices.\n"); + printf(" -i - test INPUT only.\n"); + printf(" -o - test OUTPUT only.\n"); + printf(" -t - Test level, 0=Quick, 1=Normal, 2=Exhaustive\n"); + printf(" -? - Help\n"); } /*******************************************************************/ -static int TestAdvance( int mode, PaDeviceIndex deviceID, double sampleRate, - int numChannels, PaSampleFormat format ) +int main( int argc, char **argv ); +int main( int argc, char **argv ) { - PaStreamParameters inputParameters, outputParameters, *ipp, *opp; - PaStream *stream = NULL; - PaError result = paNoError; - PaQaData myData; - #define FRAMES_PER_BUFFER (64) - const int kNumSeconds = 100; - - /* Setup data for synthesis thread. */ - myData.framesLeft = (unsigned long) (sampleRate * kNumSeconds); - myData.numChannels = numChannels; - myData.mode = mode; - myData.format = format; - switch( format ) - { - case paFloat32: - case paInt32: - case paInt24: - myData.bytesPerSample = 4; - break; -/* case paPackedInt24: - myData.bytesPerSample = 3; - break; */ - default: - myData.bytesPerSample = 2; - break; - } + int i; + PaError result; + int allDevices = 0; + int testOutput = 1; + int testInput = 1; + int testLevel = TEST_LEVEL_NORMAL; + char *executableName = argv[0]; - if( mode == MODE_INPUT ) - { - inputParameters.device = deviceID; - inputParameters.channelCount = numChannels; - inputParameters.sampleFormat = format; - inputParameters.suggestedLatency = - Pa_GetDeviceInfo( inputParameters.device )->defaultLowInputLatency; - inputParameters.hostApiSpecificStreamInfo = NULL; - ipp = &inputParameters; - } - else + /* Parse command line parameters. */ + i = 1; + while( idefaultLowOutputLatency; - outputParameters.hostApiSpecificStreamInfo = NULL; - opp = &outputParameters; - } - else - { - opp = NULL; + default: + printf("Illegal option: %s\n", arg); + case '?': + usage( executableName ); + exit(1); + break; + } + } + else + { + printf("Illegal argument: %s\n", arg); + usage( executableName ); + return 1; + } + i += 1; } - if(paFormatIsSupported == Pa_IsFormatSupported( ipp, opp, sampleRate )) - { - printf("------ TestAdvance: %s, device = %d, rate = %g" - ", numChannels = %d, format = %lu -------\n", - ( mode == MODE_INPUT ) ? "INPUT" : "OUTPUT", - deviceID, sampleRate, numChannels, (unsigned long)format); - EXPECT( ((result = Pa_OpenStream( &stream, - ipp, - opp, - sampleRate, - FRAMES_PER_BUFFER, - paClipOff, /* we won't output out of range samples so don't bother clipping them */ - QaCallback, - &myData ) ) == 0) ); - if( stream ) + ASSERT_EQ(2, sizeof(short)); /* The callback assumes we have 16-bit shorts. */ + ASSERT_EQ(4, sizeof(int)); /* The callback assumes we have 32-bit ints. */ + ASSERT_EQ(paNoError, (result=Pa_Initialize())); + + if (testLevel == TEST_LEVEL_QUICK) { + printf("\n---- Quick Test ---------------\n"); + RunQuickTest(); + } else { + if( testInput ) + { + printf("\n---- Test INPUT ---------------\n"); + if (testLevel == TEST_LEVEL_NORMAL) { + TestNormal( MODE_INPUT, allDevices ); + } else { + TestExhaustive( MODE_INPUT, allDevices ); + } + } + if( testOutput ) { - PaTime oldStamp, newStamp; - unsigned long oldFrames; - int minDelay = ( mode == MODE_INPUT ) ? 1000 : 400; - /* Was: - int minNumBuffers = Pa_GetMinNumBuffers( FRAMES_PER_BUFFER, sampleRate ); - int msec = (int) ((minNumBuffers * 3 * 1000.0 * FRAMES_PER_BUFFER) / sampleRate); - */ - int msec = (int)( 3.0 * - (( mode == MODE_INPUT ) ? inputParameters.suggestedLatency : outputParameters.suggestedLatency )); - if( msec < minDelay ) msec = minDelay; - printf("msec = %d\n", msec); /**/ - EXPECT( ((result=Pa_StartStream( stream )) == 0) ); - /* Check to make sure PortAudio is advancing timeStamp. */ - oldStamp = Pa_GetStreamTime(stream); - Pa_Sleep(msec); - newStamp = Pa_GetStreamTime(stream); - printf("oldStamp = %9.6f, newStamp = %9.6f\n", oldStamp, newStamp ); /**/ - EXPECT( (oldStamp < newStamp) ); - /* Check to make sure callback is decrementing framesLeft. */ - oldFrames = myData.framesLeft; - Pa_Sleep(msec); - printf("oldFrames = %lu, myData.framesLeft = %lu\n", oldFrames, myData.framesLeft ); /**/ - EXPECT( (oldFrames > myData.framesLeft) ); - EXPECT( ((result=Pa_CloseStream( stream )) == 0) ); - stream = NULL; + printf("\n---- Test OUTPUT ---------------\n"); + if (testLevel == TEST_LEVEL_NORMAL) { + TestNormal( MODE_OUTPUT, allDevices ); + } else { + TestExhaustive( MODE_OUTPUT, allDevices ); + } } } - return 0; + error: - if( stream != NULL ) Pa_CloseStream( stream ); - return -1; + ASSERT_EQ(paNoError, Pa_Terminate()); + + PAQA_PRINT_RESULT; + return PAQA_EXIT_RESULT; } diff --git a/3rdparty/portaudio/qa/paqa_errs.c b/3rdparty/portaudio/qa/paqa_errs.c index 8d4094f94c3ae..3c66f2c58138d 100644 --- a/3rdparty/portaudio/qa/paqa_errs.c +++ b/3rdparty/portaudio/qa/paqa_errs.c @@ -43,10 +43,13 @@ * license above. */ +#include #include +#include /* for EXIT_SUCCESS and EXIT_FAILURE */ #include #include "portaudio.h" +#include "paqa_macros.h" /*--------- Definitions ---------*/ #define MODE_INPUT (0) @@ -54,6 +57,8 @@ #define FRAMES_PER_BUFFER (64) #define SAMPLE_RATE (44100.0) +PAQA_INSTANTIATE_GLOBALS + typedef struct PaQaData { unsigned long framesLeft; @@ -63,38 +68,6 @@ typedef struct PaQaData } PaQaData; -static int gNumPassed = 0; /* Two globals */ -static int gNumFailed = 0; - -/*------------------- Macros ------------------------------*/ -/* Print ERROR if it fails. Tally success or failure. Odd */ -/* do-while wrapper seems to be needed for some compilers. */ - -#define EXPECT(_exp) \ - do \ - { \ - if ((_exp)) {\ - gNumPassed++; \ - } \ - else { \ - printf("\nERROR - 0x%x - %s for %s\n", result, Pa_GetErrorText(result), #_exp ); \ - gNumFailed++; \ - goto error; \ - } \ - } while(0) - -#define HOPEFOR(_exp) \ - do \ - { \ - if ((_exp)) {\ - gNumPassed++; \ - } \ - else { \ - printf("\nERROR - 0x%x - %s for %s\n", result, Pa_GetErrorText(result), #_exp ); \ - gNumFailed++; \ - } \ - } while(0) - /*-------------------------------------------------------------------------*/ /* This routine will be called by the PortAudio engine when audio is needed. It may be called at interrupt level on some machines so don't do anything @@ -393,11 +366,14 @@ int main(void) { PaError result; - EXPECT(((result = Pa_Initialize()) == paNoError)); + printf("-----------------------------\n"); + printf("paqa_errs - PortAudio QA test\n"); + ASSERT_EQ(paNoError, (result = Pa_Initialize())); TestBadOpens(); TestBadActions(); error: Pa_Terminate(); - printf("QA Report: %d passed, %d failed.\n", gNumPassed, gNumFailed); - return 0; + + PAQA_PRINT_RESULT; + return PAQA_EXIT_RESULT; } diff --git a/3rdparty/portaudio/qa/paqa_latency.c b/3rdparty/portaudio/qa/paqa_latency.c index a70807b5119f4..9f468bf53ac3f 100644 --- a/3rdparty/portaudio/qa/paqa_latency.c +++ b/3rdparty/portaudio/qa/paqa_latency.c @@ -61,8 +61,8 @@ typedef struct int left_phase; int right_phase; char message[20]; - int minFramesPerBuffer; - int maxFramesPerBuffer; + unsigned long minFramesPerBuffer; + unsigned long maxFramesPerBuffer; int callbackCount; PaTime minDeltaDacTime; PaTime maxDeltaDacTime; @@ -170,8 +170,8 @@ PaError paqaCheckLatency( PaStreamParameters *outputParamsPtr, printf("Play for %d seconds.\n", NUM_SECONDS ); Pa_Sleep( NUM_SECONDS * 1000 ); - printf(" minFramesPerBuffer = %4d\n", dataPtr->minFramesPerBuffer ); - printf(" maxFramesPerBuffer = %4d\n", dataPtr->maxFramesPerBuffer ); + printf(" minFramesPerBuffer = %4lu\n", dataPtr->minFramesPerBuffer ); + printf(" maxFramesPerBuffer = %4lu\n", dataPtr->maxFramesPerBuffer ); printf(" minDeltaDacTime = %f\n", dataPtr->minDeltaDacTime ); printf(" maxDeltaDacTime = %f\n", dataPtr->maxDeltaDacTime ); diff --git a/3rdparty/portaudio/qa/paqa_macros.h b/3rdparty/portaudio/qa/paqa_macros.h new file mode 100644 index 0000000000000..5de07585b3e06 --- /dev/null +++ b/3rdparty/portaudio/qa/paqa_macros.h @@ -0,0 +1,71 @@ + +#ifndef PORTAUDIO_QA_PAQA_MACROS_H +#define PORTAUDIO_QA_PAQA_MACROS_H + +extern int paQaNumPassed; +extern int paQaNumFailed; + +/* You must use this macro exactly once in each test program. */ +#define PAQA_INSTANTIATE_GLOBALS\ + int paQaNumPassed = 0;\ + int paQaNumFailed = 0; + +/*------------------- Macros ------------------------------*/ +/* Print ERROR if it fails. Tally success or failure. Odd */ +/* do-while wrapper seems to be needed for some compilers. */ +#define ASSERT_TRUE(_exp) \ + do \ + { \ + if (_exp) {\ + paQaNumPassed++; \ + } \ + else { \ + printf("ERROR at %s:%d, (%s) not true\n", \ + __FILE__, __LINE__, #_exp ); \ + paQaNumFailed++; \ + goto error; \ + } \ + } while(0) + +#define ASSERT_AB(_a, _b, _op, _opn) \ + do \ + { \ + int mA = (int)(_a); \ + int mB = (int)(_b); \ + if (mA _op mB) {\ + paQaNumPassed++; \ + } \ + else { \ + printf("ERROR at %s:%d, (%s) %s (%s), %d %s %d\n", \ + __FILE__, __LINE__, #_a, #_opn, #_b, mA, #_opn, mB ); \ + paQaNumFailed++; \ + goto error; \ + } \ + } while(0) + +#define ASSERT_EQ(_a, _b) ASSERT_AB(_a, _b, ==, !=) +#define ASSERT_NE(_a, _b) ASSERT_AB(_a, _b, !=, ==) +#define ASSERT_GT(_a, _b) ASSERT_AB(_a, _b, >, <=) +#define ASSERT_GE(_a, _b) ASSERT_AB(_a, _b, >=, <) +#define ASSERT_LT(_a, _b) ASSERT_AB(_a, _b, <, >=) +#define ASSERT_LE(_a, _b) ASSERT_AB(_a, _b, <=, >) + +#define HOPEFOR(_exp) \ + do \ + { \ + if ((_exp)) {\ + paQaNumPassed++; \ + } \ + else { \ + printf("\nERROR - 0x%x - %s for %s\n", result, Pa_GetErrorText(result), #_exp ); \ + paQaNumFailed++; \ + } \ + } while(0) + +#define PAQA_PRINT_RESULT \ + printf("QA Report: %d passed, %d failed.\n", paQaNumPassed, paQaNumFailed ) + +#define PAQA_EXIT_RESULT \ + (((paQaNumFailed > 0) || (paQaNumPassed == 0)) ? EXIT_FAILURE : EXIT_SUCCESS) + +#endif /* PORTAUDIO_QA_PAQA_MACROS_H */ diff --git a/3rdparty/portaudio/src/common/pa_converters.c b/3rdparty/portaudio/src/common/pa_converters.c index 29bbf2b3f7b74..dbf0523561d32 100644 --- a/3rdparty/portaudio/src/common/pa_converters.c +++ b/3rdparty/portaudio/src/common/pa_converters.c @@ -41,8 +41,6 @@ @brief Conversion function implementations. - If the C9x function lrintf() is available, define PA_USE_C99_LRINTF to use it - @todo Consider whether functions which dither but don't clip should exist, V18 automatically enabled clipping whenever dithering was selected. Perhaps we should do the same. @@ -343,13 +341,8 @@ static void Float32_To_Int32( while( count-- ) { /* REVIEW */ -#ifdef PA_USE_C99_LRINTF - float scaled = *src * 0x7FFFFFFF; - *dest = lrintf(scaled-0.5f); -#else double scaled = *src * 0x7FFFFFFF; *dest = (PaInt32) scaled; -#endif src += sourceStride; dest += destinationStride; @@ -369,17 +362,11 @@ static void Float32_To_Int32_Dither( while( count-- ) { /* REVIEW */ -#ifdef PA_USE_C99_LRINTF - float dither = PaUtil_GenerateFloatTriangularDither( ditherGenerator ); - /* use smaller scaler to prevent overflow when we add the dither */ - float dithered = ((float)*src * (2147483646.0f)) + dither; - *dest = lrintf(dithered - 0.5f); -#else double dither = PaUtil_GenerateFloatTriangularDither( ditherGenerator ); /* use smaller scaler to prevent overflow when we add the dither */ double dithered = ((double)*src * (2147483646.0)) + dither; *dest = (PaInt32) dithered; -#endif + src += sourceStride; dest += destinationStride; } @@ -399,15 +386,9 @@ static void Float32_To_Int32_Clip( while( count-- ) { /* REVIEW */ -#ifdef PA_USE_C99_LRINTF - float scaled = *src * 0x7FFFFFFF; - PA_CLIP_( scaled, -2147483648.f, 2147483647.f ); - *dest = lrintf(scaled-0.5f); -#else double scaled = *src * 0x7FFFFFFF; PA_CLIP_( scaled, -2147483648., 2147483647. ); *dest = (PaInt32) scaled; -#endif src += sourceStride; dest += destinationStride; @@ -427,19 +408,11 @@ static void Float32_To_Int32_DitherClip( while( count-- ) { /* REVIEW */ -#ifdef PA_USE_C99_LRINTF - float dither = PaUtil_GenerateFloatTriangularDither( ditherGenerator ); - /* use smaller scaler to prevent overflow when we add the dither */ - float dithered = ((float)*src * (2147483646.0f)) + dither; - PA_CLIP_( dithered, -2147483648.f, 2147483647.f ); - *dest = lrintf(dithered-0.5f); -#else double dither = PaUtil_GenerateFloatTriangularDither( ditherGenerator ); /* use smaller scaler to prevent overflow when we add the dither */ double dithered = ((double)*src * (2147483646.0)) + dither; PA_CLIP_( dithered, -2147483648., 2147483647. ); *dest = (PaInt32) dithered; -#endif src += sourceStride; dest += destinationStride; @@ -601,13 +574,8 @@ static void Float32_To_Int16( while( count-- ) { -#ifdef PA_USE_C99_LRINTF - float tempf = (*src * (32767.0f)) ; - *dest = lrintf(tempf-0.5f); -#else short samp = (short) (*src * (32767.0f)); *dest = samp; -#endif src += sourceStride; dest += destinationStride; @@ -631,11 +599,7 @@ static void Float32_To_Int16_Dither( /* use smaller scaler to prevent overflow when we add the dither */ float dithered = (*src * (32766.0f)) + dither; -#ifdef PA_USE_C99_LRINTF - *dest = lrintf(dithered-0.5f); -#else *dest = (PaInt16) dithered; -#endif src += sourceStride; dest += destinationStride; @@ -655,11 +619,8 @@ static void Float32_To_Int16_Clip( while( count-- ) { -#ifdef PA_USE_C99_LRINTF - long samp = lrintf((*src * (32767.0f)) -0.5f); -#else long samp = (PaInt32) (*src * (32767.0f)); -#endif + PA_CLIP_( samp, -0x8000, 0x7FFF ); *dest = (PaInt16) samp; @@ -687,11 +648,7 @@ static void Float32_To_Int16_DitherClip( float dithered = (*src * (32766.0f)) + dither; PaInt32 samp = (PaInt32) dithered; PA_CLIP_( samp, -0x8000, 0x7FFF ); -#ifdef PA_USE_C99_LRINTF - *dest = lrintf(samp-0.5f); -#else *dest = (PaInt16) samp; -#endif src += sourceStride; dest += destinationStride; diff --git a/3rdparty/portaudio/src/common/pa_front.c b/3rdparty/portaudio/src/common/pa_front.c index 89b4335b8d4e0..9f81f26733f64 100644 --- a/3rdparty/portaudio/src/common/pa_front.c +++ b/3rdparty/portaudio/src/common/pa_front.c @@ -154,6 +154,7 @@ static PaUtilHostApiRepresentation **hostApis_ = 0; static int hostApisCount_ = 0; static int defaultHostApiIndex_ = 0; static int initializationCount_ = 0; +static int initializing_ = 0; static int deviceCount_ = 0; PaUtilStreamRepresentation *firstOpenStream_ = NULL; @@ -360,8 +361,21 @@ PaError Pa_Initialize( void ) ++initializationCount_; result = paNoError; } + else if( initializing_ ) + { + // a concurrent initialization is already running + PA_DEBUG(("Attempting to re-enter Pa_Initialize(), aborting!\n")); + result = paCanNotInitializeRecursively; + } else { + // set initializing_ here to + // let recursive calls execute the if branch above. + // This can happen if a driver like FlexAsio itself uses portaudio + // and avoids a stack overflow in the user application. + // https://github.com/PortAudio/portaudio/issues/766 + initializing_ = 1; + PA_VALIDATE_TYPE_SIZES; PA_VALIDATE_ENDIANNESS; @@ -371,6 +385,8 @@ PaError Pa_Initialize( void ) result = InitializeHostApis(); if( result == paNoError ) ++initializationCount_; + + initializing_ = 0; } PA_LOGAPI_EXIT_PAERROR( "Pa_Initialize", result ); @@ -453,6 +469,7 @@ const char *Pa_GetErrorText( PaError errorCode ) case paCanNotWriteToAnInputOnlyStream: result = "Can't write to an input only stream"; break; case paIncompatibleStreamHostApi: result = "Incompatible stream host API"; break; case paBadBufferPtr: result = "Bad buffer pointer"; break; + case paCanNotInitializeRecursively: result = "PortAudio can not be initialized recursively"; break; default: if( errorCode > 0 ) result = "Invalid error code (value greater than zero)"; diff --git a/3rdparty/portaudio/src/common/pa_hostapi.h b/3rdparty/portaudio/src/common/pa_hostapi.h index 4ac3ab60e9299..c716fa7e55a94 100644 --- a/3rdparty/portaudio/src/common/pa_hostapi.h +++ b/3rdparty/portaudio/src/common/pa_hostapi.h @@ -67,7 +67,7 @@ are defaulted to 1. #define PA_USE_SKELETON 1 #endif -#if defined(PA_NO_ASIO) || defined(PA_NO_DS) || defined(PA_NO_WMME) || defined(PA_NO_WASAPI) || defined(PA_NO_WDMKS) +#if defined(PA_NO_PULSEAUDIO) || defined(PA_NO_ASIO) || defined(PA_NO_DS) || defined(PA_NO_WMME) || defined(PA_NO_WASAPI) || defined(PA_NO_WDMKS) #error "Portaudio: PA_NO_ is no longer supported, please remove definition and use PA_USE_ instead" #endif @@ -132,6 +132,13 @@ are defaulted to 1. #define PA_USE_JACK 1 #endif +#ifndef PA_USE_PULSEAUDIO +#define PA_USE_PULSEAUDIO 0 +#elif (PA_USE_PULSEAUDIO != 0) && (PA_USE_PULSEAUDIO != 1) +#undef PA_USE_PULSEAUDIO +#define PA_USE_PULSEAUDIO 1 +#endif + #ifndef PA_USE_SGI #define PA_USE_SGI 0 #elif (PA_USE_SGI != 0) && (PA_USE_SGI != 1) diff --git a/3rdparty/portaudio/src/common/pa_util.h b/3rdparty/portaudio/src/common/pa_util.h index fb4b329663cc9..5e3909ab43d0f 100644 --- a/3rdparty/portaudio/src/common/pa_util.h +++ b/3rdparty/portaudio/src/common/pa_util.h @@ -154,6 +154,12 @@ void PaUtil_InitializeClock( void ); /** Return the system time in seconds. Used to implement CPU load functions + @note Do not make assumptions about which underlying clock is used to implement + PaUtil_GetTime, or use the current implementation as a guide. Do not use this + function when a specific clock is required (e.g. when using platform APIs + such as pthreads). If you need to use a specific clock, use a native API that + returns that clock. + @see PaUtil_InitializeClock */ double PaUtil_GetTime( void ); diff --git a/3rdparty/portaudio/src/hostapi/dsound/pa_win_ds.c b/3rdparty/portaudio/src/hostapi/dsound/pa_win_ds.c index a1f6892dd5b10..93e6a59ce3f0c 100644 --- a/3rdparty/portaudio/src/hostapi/dsound/pa_win_ds.c +++ b/3rdparty/portaudio/src/hostapi/dsound/pa_win_ds.c @@ -93,6 +93,7 @@ #include "pa_win_waveformat.h" #include "pa_win_wdmks_utils.h" #include "pa_win_coinitialize.h" +#include "pa_win_version.h" #if (defined(WIN32) && (defined(_MSC_VER) && (_MSC_VER >= 1200))) /* MSC version 6 and above */ #pragma comment( lib, "dsound.lib" ) @@ -328,43 +329,26 @@ typedef struct PaWinDsStream */ static double PaWinDS_GetMinSystemLatencySeconds( void ) { -/* -NOTE: GetVersionEx() is deprecated as of Windows 8.1 and can not be used to reliably detect -versions of Windows higher than Windows 8 (due to manifest requirements for reporting higher versions). -Microsoft recommends switching to VerifyVersionInfo (available on Win 2k and later), however GetVersionEx -is faster, for now we just disable the deprecation warning. -See: https://msdn.microsoft.com/en-us/library/windows/desktop/ms724451(v=vs.85).aspx -See: http://www.codeproject.com/Articles/678606/Part-Overcoming-Windows-s-deprecation-of-GetVe -*/ -#pragma warning (disable : 4996) /* use of GetVersionEx */ - double minLatencySeconds; /* Set minimal latency based on whether NT or other OS. * NT has higher latency. */ + PaOsVersion version = PaWinUtil_GetOsVersion(); - OSVERSIONINFO osvi; - osvi.dwOSVersionInfoSize = sizeof( osvi ); - GetVersionEx( &osvi ); - DBUG(("PA - PlatformId = 0x%x\n", osvi.dwPlatformId )); - DBUG(("PA - MajorVersion = 0x%x\n", osvi.dwMajorVersion )); - DBUG(("PA - MinorVersion = 0x%x\n", osvi.dwMinorVersion )); - /* Check for NT */ - if( (osvi.dwMajorVersion == 4) && (osvi.dwPlatformId == 2) ) + if(version <= paOsVersionWindows9x) { - minLatencySeconds = PA_DS_WIN_NT_DEFAULT_LATENCY_; + minLatencySeconds = PA_DS_WIN_9X_DEFAULT_LATENCY_; } - else if(osvi.dwMajorVersion >= 5) + else if(version == paOsVersionWindowsNT4) { - minLatencySeconds = PA_DS_WIN_WDM_DEFAULT_LATENCY_; + minLatencySeconds = PA_DS_WIN_NT_DEFAULT_LATENCY_; } - else + else if(version >= paOsVersionWindows2000) { - minLatencySeconds = PA_DS_WIN_9X_DEFAULT_LATENCY_; + minLatencySeconds = PA_DS_WIN_WDM_DEFAULT_LATENCY_; } - return minLatencySeconds; -#pragma warning (default : 4996) + return minLatencySeconds; } diff --git a/3rdparty/portaudio/src/hostapi/jack/pa_jack.c b/3rdparty/portaudio/src/hostapi/jack/pa_jack.c index 746885fb64e7d..5c20b4420d070 100644 --- a/3rdparty/portaudio/src/hostapi/jack/pa_jack.c +++ b/3rdparty/portaudio/src/hostapi/jack/pa_jack.c @@ -507,7 +507,7 @@ static PaError BuildDeviceList( PaJackHostApiRepresentation *jackApi ) // Add 1 for null terminator. size_t device_name_regex_escaped_size = jack_client_name_size() * 2 + 1; size_t port_regex_size = device_name_regex_escaped_size + strlen(port_regex_suffix); - int port_index, client_index, i; + unsigned long port_index, client_index, i; double globalSampleRate; regex_t port_regex; unsigned long numClients = 0, numPorts = 0; diff --git a/3rdparty/portaudio/src/hostapi/pulseaudio/pa_linux_pulseaudio.c b/3rdparty/portaudio/src/hostapi/pulseaudio/pa_linux_pulseaudio.c new file mode 100644 index 0000000000000..e784b65eeb03c --- /dev/null +++ b/3rdparty/portaudio/src/hostapi/pulseaudio/pa_linux_pulseaudio.c @@ -0,0 +1,1452 @@ + +/* + * PulseAudio host to play natively in Linux based systems without + * ALSA emulation + * + * Copyright (c) 2014-2023 Tuukka Pasanen + * Copyright (c) 2016 Sqweek + * Copyright (c) 2020 Daniel Schurmann + * + * Based on the Open Source API proposed by Ross Bencina + * Copyright (c) 1999-2002 Ross Bencina, Phil Burk + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/* + * The text above constitutes the entire PortAudio license; however, + * the PortAudio community also makes the following non-binding requests: + * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. It is also + * requested that these non-binding requests be included along with the + * license above. + */ + +/** @file + @ingroup common_src + + @brief PulseAudio implementation of support for a host API. + + This host API implements PulseAudio support for portaudio + it has callbackmode and normal write mode support +*/ + + +#include /* strlen() */ + +#include "pa_linux_pulseaudio_cb_internal.h" +#include "pa_linux_pulseaudio_block_internal.h" + +/* PulseAudio headers */ +#include +#include +#include + +/* This is used to identify process name for PulseAudio. */ +extern char *__progname; + +/* PulseAudio specific functions */ +int PaPulseAudio_CheckConnection( PaPulseAudio_HostApiRepresentation * ptr ) +{ + pa_context_state_t state; + + + /* Sanity check if ptr if NULL don't go anywhere or + * it will SIGSEGV + */ + if( !ptr ) + { + PA_PULSEAUDIO_SET_LAST_HOST_ERROR( 0, + "PaPulseAudio_CheckConnection: Host API is NULL! Can't do anything about it" ); + return -1; + } + + if( !ptr->context || !ptr->mainloop ) + { + PA_PULSEAUDIO_SET_LAST_HOST_ERROR( 0, + "PaPulseAudio_CheckConnection: PulseAudio context or mainloop are NULL" ); + return -1; + } + + state = pa_context_get_state(ptr->context); + + if( !PA_CONTEXT_IS_GOOD(state) ) + { + switch( state ) + { + /* These can be found from + * https://freedesktop.org/software/pulseaudio/doxygen/def_8h.html + */ + + case PA_CONTEXT_UNCONNECTED: + PA_PULSEAUDIO_SET_LAST_HOST_ERROR( 0, + "PaPulseAudio_CheckConnection: The context hasn't been connected yet (PA_CONTEXT_UNCONNECTED)" ); + break; + + case PA_CONTEXT_FAILED: + PA_PULSEAUDIO_SET_LAST_HOST_ERROR( 0, + "PaPulseAudio_CheckConnection: The connection failed or was disconnected. (PA_CONTEXT_FAILED)" ); + break; + } + + return -1; + } + return 0; +} + +/* Create HostAPI presensentation */ +PaPulseAudio_HostApiRepresentation *PaPulseAudio_New( void ) +{ + PaPulseAudio_HostApiRepresentation *ptr; + int fd[2] = { -1, -1 }; + char pulseaudioDeviceName[PAPULSEAUDIO_MAX_DEVICENAME]; + + ptr = (PaPulseAudio_HostApiRepresentation *) + PaUtil_AllocateZeroInitializedMemory(sizeof(PaPulseAudio_HostApiRepresentation)); + + /* ptr is NULL if runs out of memory or pointer to allocated memory */ + if( !ptr ) + { + PA_PULSEAUDIO_SET_LAST_HOST_ERROR( 0, + "PaPulseAudio_HostApiRepresentation: Can't allocate memory required for using PulseAudio" ); + return NULL; + } + + /* Make sure we have NULL all struct first */ + memset(ptr, 0x00, sizeof(PaPulseAudio_HostApiRepresentation)); + + ptr->mainloop = pa_threaded_mainloop_new(); + + if( !ptr->mainloop ) + { + PA_PULSEAUDIO_SET_LAST_HOST_ERROR( 0, + "PaPulseAudio_HostApiRepresentation: Can't allocate PulseAudio mainloop" ); + goto fail; + } + + ptr->mainloopApi = pa_threaded_mainloop_get_api(ptr->mainloop); + + /* Use program name as PulseAudio device name */ + snprintf( pulseaudioDeviceName, PAPULSEAUDIO_MAX_DEVICENAME, "%s", __progname ); + + ptr->context = + pa_context_new( pa_threaded_mainloop_get_api(ptr->mainloop), pulseaudioDeviceName ); + + if( !ptr->context ) + { + PA_PULSEAUDIO_SET_LAST_HOST_ERROR( 0, + "PaPulseAudio_HostApiRepresentation: Can't instantiate PulseAudio context" ); + goto fail; + } + + pa_context_set_state_callback( ptr->context, PaPulseAudio_CheckContextStateCb, + ptr ); + + + if( pa_threaded_mainloop_start( ptr->mainloop ) < 0 ) + { + PA_PULSEAUDIO_SET_LAST_HOST_ERROR( 0, + "PaPulseAudio_HostApiRepresentation: PulseAudio can't start mainloop" ); + goto fail; + } + + ptr->deviceCount = 0; + + return ptr; + + fail: + PaPulseAudio_Free( ptr ); + return NULL; +} + +/* Free HostAPI */ +void PaPulseAudio_Free( PaPulseAudio_HostApiRepresentation * ptr ) +{ + + /* Sanity check if ptr if NULL don't go anywhere or + * it will SIGSEGV + */ + if( !ptr ) + { + PA_PULSEAUDIO_SET_LAST_HOST_ERROR( 0, + "PaPulseAudio_Free: Host API is NULL! Can't do anything about it" ); + return; + } + + if( ptr->mainloop ) + { + pa_threaded_mainloop_stop( ptr->mainloop ); + } + + if( ptr->context ) + { + pa_context_disconnect( ptr->context ); + pa_context_unref( ptr->context ); + ptr->context = NULL; + } + + if( ptr->mainloopApi && ptr->timeEvent ) + { + ptr->mainloopApi->time_free( ptr->timeEvent ); + ptr->mainloopApi = NULL; + ptr->timeEvent = NULL; + } + + + if( ptr->mainloop ) + { + pa_threaded_mainloop_free( ptr->mainloop ); + ptr->mainloop = NULL; + } + + PaUtil_FreeMemory( ptr ); +} + +/* If there is drop connection to server this one is called + * in future it should stop the stream also + */ +void PaPulseAudio_CheckContextStateCb( pa_context * c, + void *userdata ) +{ + PaPulseAudio_HostApiRepresentation *ptr = + (PaPulseAudio_HostApiRepresentation *) userdata; + /* If this is null we have big problems and we probably are out of memory */ + if( !c ) + { + PA_PULSEAUDIO_SET_LAST_HOST_ERROR( 0, + "PaPulseAudio_CheckContextStateCb: Invalid context " ); + pa_threaded_mainloop_signal( ptr->mainloop, 0 ); + return; + } + + pa_threaded_mainloop_signal( ptr->mainloop, 0 ); +} + +/* Server info callback */ +void PaPulseAudio_ServerInfoCb( pa_context *c, + const pa_server_info *i, + void *userdata ) +{ + PaPulseAudio_HostApiRepresentation *pulseaudioHostApi = + (PaPulseAudio_HostApiRepresentation *) userdata; + + if( !c || !i ) + { + PA_PULSEAUDIO_SET_LAST_HOST_ERROR( 0, + "PaPulseAudio_ServerInfoCb: Invalid context or can't get server info" ); + pa_threaded_mainloop_signal( pulseaudioHostApi->mainloop, 0 ); + return; + } + + pulseaudioHostApi->pulseaudioDefaultSampleSpec = i->sample_spec; + + pa_threaded_mainloop_signal( pulseaudioHostApi->mainloop, 0 ); +} + +/* Function adds device to list. It can be input or output stream + * or in pulseaudio source or sink. + */ +int _PaPulseAudio_AddAudioDevice( PaPulseAudio_HostApiRepresentation *hostapi, + const char *PaPulseAudio_SinkSourceName, + const char *PaPulseAudio_SinkSourceNameDesc, + int inputChannels, + int outputChannels, + double defaultLowInputLatency, + double defaultHighInputLatency, + double defaultLowOutputLatency, + double defaultHighOutputLatency, + const long defaultSampleRate ) +{ + /* These should be at least 1 + * + * Maximun size of string is 1024 (PAPULSEAUDIO_MAX_DEVICENAME) + * which should be mostly suffient even pulseaudio device + * names can be very long + */ + int pulseaudioRealNameSize = strnlen( PaPulseAudio_SinkSourceNameDesc, (PAPULSEAUDIO_MAX_DEVICENAME - 1) ) + 1; + int pulseaudioDeviceNameSize = strnlen( PaPulseAudio_SinkSourceName, (PAPULSEAUDIO_MAX_DEVICENAME - 1) ) + 1; + char *pulseaudioLocalDeviceName = NULL; + + hostapi->deviceInfoArray[hostapi->deviceCount].structVersion = 2; + hostapi->deviceInfoArray[hostapi->deviceCount].hostApi = hostapi->hostApiIndex; + hostapi->pulseaudioDeviceNames[hostapi->deviceCount] = + PaUtil_GroupAllocateZeroInitializedMemory( hostapi->allocations, + pulseaudioRealNameSize ); + pulseaudioLocalDeviceName = PaUtil_GroupAllocateZeroInitializedMemory( hostapi->allocations, + pulseaudioDeviceNameSize ); + if( !hostapi->pulseaudioDeviceNames[hostapi->deviceCount] && + !pulseaudioLocalDeviceName ) + { + PA_PULSEAUDIO_SET_LAST_HOST_ERROR( 0, + "_PaPulseAudio_AddAudioDevice: Can't alloc memory" ); + return paInsufficientMemory; + } + + /* We can maximum have 1024 (PAPULSEAUDIO_MAX_DEVICECOUNT) + * devices where to choose which should be mostly enough + */ + if( hostapi->deviceCount >= PAPULSEAUDIO_MAX_DEVICECOUNT ) + { + return paDeviceUnavailable; + } + + snprintf( hostapi->pulseaudioDeviceNames[hostapi->deviceCount], + pulseaudioRealNameSize, + "%s", + PaPulseAudio_SinkSourceNameDesc ); + snprintf( pulseaudioLocalDeviceName, + pulseaudioDeviceNameSize, + "%s", + PaPulseAudio_SinkSourceName ); + + + hostapi->deviceInfoArray[hostapi->deviceCount].name = pulseaudioLocalDeviceName; + + hostapi->deviceInfoArray[hostapi->deviceCount].maxInputChannels = inputChannels; + hostapi->deviceInfoArray[hostapi->deviceCount].maxOutputChannels = outputChannels; + hostapi->deviceInfoArray[hostapi->deviceCount].defaultLowInputLatency = defaultLowInputLatency; + hostapi->deviceInfoArray[hostapi->deviceCount].defaultLowOutputLatency = defaultLowOutputLatency; + hostapi->deviceInfoArray[hostapi->deviceCount].defaultHighInputLatency = defaultHighInputLatency; + hostapi->deviceInfoArray[hostapi->deviceCount].defaultHighOutputLatency = defaultHighOutputLatency; + hostapi->deviceInfoArray[hostapi->deviceCount].defaultSampleRate = defaultSampleRate; + hostapi->deviceCount++; + + return paNoError; +} + +/* Called when iterating through sinks */ +void PaPulseAudio_SinkListCb( pa_context * c, + const pa_sink_info * l, + int eol, + void *userdata ) +{ + PaPulseAudio_HostApiRepresentation *pulseaudioHostApi = + (PaPulseAudio_HostApiRepresentation *) userdata; + const char *pulseaudioDeviceName = NULL; + + + /* If this is null we have big problems and we probably are out of memory */ + if( !c || !l ) + { + PA_PULSEAUDIO_SET_LAST_HOST_ERROR( 0, + "PaPulseAudio_SinkListCb: Invalid context or sink info" ); + goto error; + } + + /* If eol is set to a positive number, you're at the end of the list */ + if( eol > 0 ) + { + goto error; + } + + pulseaudioDeviceName = l->name; + + if( l->description != NULL ) + { + pulseaudioDeviceName = l->description; + } + + if( _PaPulseAudio_AddAudioDevice( pulseaudioHostApi, + pulseaudioDeviceName, + l->name, + 0, + l->sample_spec.channels, + 0, + 0, + PA_PULSEAUDIO_DEFAULT_MIN_LATENCY, + PA_PULSEAUDIO_DEFAULT_MAX_LATENCY, + l->sample_spec.rate ) != paNoError ) + { + PA_PULSEAUDIO_SET_LAST_HOST_ERROR( 0, + "PaPulseAudio_SinkListCb: Can't add device. Maximum amount reached!" ); + } + + error: + pa_threaded_mainloop_signal( pulseaudioHostApi->mainloop, + 0 ); +} + +/* Called when iterating through sources */ +void PaPulseAudio_SourceListCb( pa_context * c, + const pa_source_info * l, + int eol, + void *userdata ) +{ + PaPulseAudio_HostApiRepresentation *pulseaudioHostApi = + (PaPulseAudio_HostApiRepresentation *) userdata; + const char *pulseaudioDeviceName = NULL; + + + /* If this is null we have big problems and we probably are out of memory */ + if( !c ) + { + PA_PULSEAUDIO_SET_LAST_HOST_ERROR( 0, + "PaPulseAudio_SourceListCb: Invalid context" ); + goto error; + } + + /* If eol is set to a positive number, you're at the end of the list */ + if( eol > 0 ) + { + goto error; + } + + pulseaudioDeviceName = l->name; + + if( l->description != NULL ) + { + pulseaudioDeviceName = l->description; + } + + if( _PaPulseAudio_AddAudioDevice( pulseaudioHostApi, + pulseaudioDeviceName, + l->name, + l->sample_spec.channels, + 0, + PA_PULSEAUDIO_DEFAULT_MIN_LATENCY, + PA_PULSEAUDIO_DEFAULT_MAX_LATENCY, + 0, + 0, + l->sample_spec.rate ) != paNoError ) + { + PA_PULSEAUDIO_SET_LAST_HOST_ERROR( 0, + "PaPulseAudio_SourceListCb: Can't add device. Maximum amount reached!" ); + } + + error: + pa_threaded_mainloop_signal( pulseaudioHostApi->mainloop, + 0 ); +} + +/* This routine is called whenever the stream state changes */ +void PaPulseAudio_StreamStateCb( pa_stream * s, + void *userdata ) +{ + const pa_buffer_attr *pulseaudioBufferAttr = NULL; + /* If you need debug pring enable these + * char cmt[PA_CHANNEL_MAP_SNPRINT_MAX], sst[PA_SAMPLE_SPEC_SNPRINT_MAX]; + */ + + + /* If this is null we have big problems and we probably are out of memory */ + if( !s ) + { + PA_PULSEAUDIO_SET_LAST_HOST_ERROR( 0, + "PaPulseAudio_StreamStateCb: Invalid stream" ); + return; + } + + switch( pa_stream_get_state(s) ) + { + case PA_STREAM_TERMINATED: + break; + + case PA_STREAM_CREATING: + break; + + case PA_STREAM_READY: + if (!(pulseaudioBufferAttr = pa_stream_get_buffer_attr(s))) + { + PA_DEBUG( ("Portaudio %s: Can get buffer attr: '%s'\n", + __FUNCTION__, + pa_strerror(pa_context_errno(pa_stream_get_context(s) ) )) ); + PA_PULSEAUDIO_SET_LAST_HOST_ERROR( 0, + "PaPulseAudio_StreamStateCb: Can't get Stream pa_buffer_attr" ); + } + else + { + PA_DEBUG( ("%s: %s Buffer metrics: maxlength=%u, tlength=%u, prebuf=%u, minreq=%u, fragsize=%u\n", + __FUNCTION__, pa_stream_get_device_name(s), + pulseaudioBufferAttr->maxlength, pulseaudioBufferAttr->tlength, pulseaudioBufferAttr->prebuf, + pulseaudioBufferAttr->minreq, pulseaudioBufferAttr->maxlength, pulseaudioBufferAttr->fragsize) ); + } + break; + + case PA_STREAM_FAILED: + default: + PA_DEBUG( ("Portaudio %s: FAILED '%s'\n", + __FUNCTION__, + pa_strerror( pa_context_errno( pa_stream_get_context( s ) ) )) ); + + break; + } +} + +/* If stream is underflowed then this callback is called + * one needs to enable debug to make use os this + * + * Otherwise it's used to update error message + */ +void PaPulseAudio_StreamUnderflowCb( pa_stream *s, + void *userdata ) +{ + PaPulseAudio_Stream *stream = (PaPulseAudio_Stream *) userdata; + pa_buffer_attr *pulseaudioOutputSampleSpec = NULL; + + /* If this is null we have big problems and we probably are out of memory */ + if( !s ) + { + PA_PULSEAUDIO_SET_LAST_HOST_ERROR( 0, + "PaPulseAudio_StreamUnderflowCb: Invalid stream" ); + return; + } + + stream->outputUnderflows++; + pulseaudioOutputSampleSpec = (pa_buffer_attr *)pa_stream_get_buffer_attr(s); + PA_DEBUG( ("Portaudio %s: PulseAudio '%s' with delay: %ld stream has underflowed\n", __FUNCTION__, pa_stream_get_device_name(s), pulseaudioOutputSampleSpec->tlength) ); + + PA_PULSEAUDIO_SET_LAST_HOST_ERROR( 0, + "PaPulseAudio_StreamUnderflowCb: Pulseaudio stream underflow"); + + pa_threaded_mainloop_signal( stream->mainloop, + 0 ); +} + +/* Initialize HostAPI */ +PaError PaPulseAudio_Initialize( PaUtilHostApiRepresentation ** hostApi, + PaHostApiIndex hostApiIndex ) +{ + PaError result = paNoError; + int i; + int deviceCount; + int ret = 0; + PaPulseAudio_HostApiRepresentation *pulseaudioHostApi = NULL; + PaDeviceInfo *deviceInfoArray = NULL; + + pa_operation *pulseaudioOperation = NULL; + + pulseaudioHostApi = PaPulseAudio_New(); + + if( !pulseaudioHostApi ) + { + result = paInsufficientMemory; + goto error; + } + + pulseaudioHostApi->allocations = PaUtil_CreateAllocationGroup(); + + if( !pulseaudioHostApi->allocations ) + { + result = paInsufficientMemory; + goto error; + } + + pulseaudioHostApi->hostApiIndex = hostApiIndex; + *hostApi = &pulseaudioHostApi->inheritedHostApiRep; + (*hostApi)->info.structVersion = 1; + (*hostApi)->info.type = paPulseAudio; + (*hostApi)->info.name = "PulseAudio"; + + (*hostApi)->info.defaultInputDevice = paNoDevice; + (*hostApi)->info.defaultOutputDevice = paNoDevice; + + /* Connect to server */ + PaPulseAudio_Lock( pulseaudioHostApi->mainloop ); + ret = pa_context_connect( pulseaudioHostApi->context, + NULL, + 0, + NULL ); + + if( ret < 0 ) + { + PA_PULSEAUDIO_SET_LAST_HOST_ERROR( 0, + "PulseAudio_Initialize: Can't connect to server"); + result = paNotInitialized; + PaPulseAudio_UnLock( pulseaudioHostApi->mainloop ); + goto error; + } + + ret = 0; + + /* We should wait that PulseAudio server let us in or fails us */ + while( !ret ) + { + pa_threaded_mainloop_wait( pulseaudioHostApi->mainloop ); + + switch( pa_context_get_state( pulseaudioHostApi->context ) ) + { + case PA_CONTEXT_READY: + ret = 1; + break; + case PA_CONTEXT_TERMINATED: + case PA_CONTEXT_FAILED: + goto error; + break; + case PA_CONTEXT_UNCONNECTED: + case PA_CONTEXT_CONNECTING: + case PA_CONTEXT_AUTHORIZING: + case PA_CONTEXT_SETTING_NAME: + break; + } + } + + memset( pulseaudioHostApi->deviceInfoArray, + 0x00, + sizeof(PaDeviceInfo) * PAPULSEAUDIO_MAX_DEVICECOUNT ); + for (i = 0; i < PAPULSEAUDIO_MAX_DEVICECOUNT; i++) + { + pulseaudioHostApi->pulseaudioDeviceNames[i] = NULL; + } + + /* Get info about server. This returns Default sink and soure name. */ + pulseaudioOperation = + pa_context_get_server_info( pulseaudioHostApi->context, + PaPulseAudio_ServerInfoCb, + pulseaudioHostApi ); + + while( pa_operation_get_state( pulseaudioOperation ) == PA_OPERATION_RUNNING ) + { + pa_threaded_mainloop_wait( pulseaudioHostApi->mainloop ); + } + + pa_operation_unref( pulseaudioOperation ); + + /* Add the "Default" sink at index 0 */ + if( _PaPulseAudio_AddAudioDevice( pulseaudioHostApi, + "Default Sink", + "The PulseAudio default sink", + 0, + PA_CHANNELS_MAX, + 0, + 0, + PA_PULSEAUDIO_DEFAULT_MIN_LATENCY, + PA_PULSEAUDIO_DEFAULT_MAX_LATENCY, + pulseaudioHostApi->pulseaudioDefaultSampleSpec.rate ) != paNoError ) + { + PA_PULSEAUDIO_SET_LAST_HOST_ERROR( 0, + "PaPulseAudio_SinkListCb: Can't add device. Maximum amount reached!" ); + } else { + pulseaudioHostApi->inheritedHostApiRep.info.defaultOutputDevice = + pulseaudioHostApi->deviceCount - 1; + } + + /* Add the "Default" source at index 1 */ + if( _PaPulseAudio_AddAudioDevice( pulseaudioHostApi, + "Default Source", + "The PulseAudio default source", + PA_CHANNELS_MAX, + 0, + PA_PULSEAUDIO_DEFAULT_MIN_LATENCY, + PA_PULSEAUDIO_DEFAULT_MAX_LATENCY, + 0, + 0, + pulseaudioHostApi->pulseaudioDefaultSampleSpec.rate ) != paNoError ) + { + PA_PULSEAUDIO_SET_LAST_HOST_ERROR( 0, + "PaPulseAudio_SinkListCb: Can't add device. Maximum amount reached!" ); + } else { + pulseaudioHostApi->inheritedHostApiRep.info.defaultInputDevice = + pulseaudioHostApi->deviceCount - 1; + } + + /* List PulseAudio sinks. If found callback: PaPulseAudio_SinkListCb */ + pulseaudioOperation = + pa_context_get_sink_info_list( pulseaudioHostApi->context, + PaPulseAudio_SinkListCb, + pulseaudioHostApi ); + + while( pa_operation_get_state( pulseaudioOperation ) == PA_OPERATION_RUNNING ) + { + pa_threaded_mainloop_wait( pulseaudioHostApi->mainloop ); + } + + pa_operation_unref( pulseaudioOperation ); + + /* List PulseAudio sources. If found callback: PaPulseAudio_SourceListCb */ + pulseaudioOperation = + pa_context_get_source_info_list( pulseaudioHostApi->context, + PaPulseAudio_SourceListCb, + pulseaudioHostApi ); + + while( pa_operation_get_state( pulseaudioOperation ) == PA_OPERATION_RUNNING ) + { + pa_threaded_mainloop_wait( pulseaudioHostApi->mainloop ); + } + + pa_operation_unref( pulseaudioOperation ); + + (*hostApi)->info.deviceCount = pulseaudioHostApi->deviceCount; + + if( pulseaudioHostApi->deviceCount > 0 ) + { + /* If you have over 1024 Audio devices.. shame on you! */ + + (*hostApi)->deviceInfos = + (PaDeviceInfo **) + PaUtil_GroupAllocateZeroInitializedMemory( pulseaudioHostApi->allocations, + sizeof(PaDeviceInfo *) * + pulseaudioHostApi->deviceCount ); + + if( !(*hostApi)->deviceInfos ) + { + result = paInsufficientMemory; + goto error; + } + + for ( i = 0; i < pulseaudioHostApi->deviceCount; i++ ) + { + (*hostApi)->deviceInfos[i] = + &pulseaudioHostApi->deviceInfoArray[i]; + } + } + + (*hostApi)->Terminate = Terminate; + (*hostApi)->OpenStream = OpenStream; + (*hostApi)->IsFormatSupported = IsFormatSupported; + + PaUtil_InitializeStreamInterface( &pulseaudioHostApi->callbackStreamInterface, + PaPulseAudio_CloseStreamCb, + PaPulseAudio_StartStreamCb, + PaPulseAudio_StopStreamCb, + PaPulseAudio_AbortStreamCb, + IsStreamStopped, + IsStreamActive, + GetStreamTime, + GetStreamCpuLoad, + PaUtil_DummyRead, + PaUtil_DummyWrite, + PaUtil_DummyGetReadAvailable, + PaUtil_DummyGetWriteAvailable ); + + PaUtil_InitializeStreamInterface( &pulseaudioHostApi->blockingStreamInterface, + PaPulseAudio_CloseStreamCb, + PaPulseAudio_StartStreamCb, + PaPulseAudio_StopStreamCb, + PaPulseAudio_AbortStreamCb, + IsStreamStopped, + IsStreamActive, + GetStreamTime, + PaUtil_DummyGetCpuLoad, + PaPulseAudio_ReadStreamBlock, + PaPulseAudio_WriteStreamBlock, + PaPulseAudio_GetStreamReadAvailableBlock, + PaUtil_DummyGetWriteAvailable ); + + PaPulseAudio_UnLock( pulseaudioHostApi->mainloop ); + return result; + + error: + + if( pulseaudioHostApi ) + { + if( pulseaudioHostApi->allocations ) + { + PaUtil_FreeAllAllocations( pulseaudioHostApi->allocations ); + PaUtil_DestroyAllocationGroup( pulseaudioHostApi->allocations ); + } + + PaPulseAudio_Free( pulseaudioHostApi ); + pulseaudioHostApi = NULL; + } + + return result; +} + +/* Drop stream now */ +void Terminate( struct PaUtilHostApiRepresentation *hostApi ) +{ + PaPulseAudio_HostApiRepresentation *pulseaudioHostApi = + (PaPulseAudio_HostApiRepresentation *) hostApi; + + if( pulseaudioHostApi->allocations ) + { + PaUtil_FreeAllAllocations( pulseaudioHostApi->allocations ); + PaUtil_DestroyAllocationGroup( pulseaudioHostApi->allocations ); + } + + PaPulseAudio_Lock( pulseaudioHostApi->mainloop ); + pa_context_disconnect( pulseaudioHostApi->context ); + PaPulseAudio_UnLock( pulseaudioHostApi->mainloop ); + + PaPulseAudio_Free( pulseaudioHostApi ); +} + +/* Checks from pulseaudio that is format supported */ +PaError IsFormatSupported( struct PaUtilHostApiRepresentation *hostApi, + const PaStreamParameters * inputParameters, + const PaStreamParameters * outputParameters, + double sampleRate ) +{ + int inputChannelCount, + outputChannelCount; + PaSampleFormat inputSampleFormat, + outputSampleFormat; + + if( inputParameters ) + { + inputChannelCount = inputParameters->channelCount; + inputSampleFormat = inputParameters->sampleFormat; + + /* all standard sample formats are supported by the buffer adapter, + * this implementation doesn't support any custom sample formats */ + if( inputSampleFormat & paCustomFormat ) + { + return paSampleFormatNotSupported; + } + + /* unless alternate device specification is supported, reject the use of + * paUseHostApiSpecificDeviceSpecification */ + + if( inputParameters->device == paUseHostApiSpecificDeviceSpecification ) + { + return paInvalidDevice; + } + + /* check that input device can support inputChannelCount */ + if( inputChannelCount > + hostApi->deviceInfos[inputParameters->device]->maxInputChannels ) + { + return paInvalidChannelCount; + } + + /* validate inputStreamInfo */ + if( inputParameters->hostApiSpecificStreamInfo ) + { + return paIncompatibleHostApiSpecificStreamInfo; /* this implementation doesn't use custom stream info */ + } + + } + + else + { + inputChannelCount = 0; + } + + if( outputParameters ) + { + outputChannelCount = outputParameters->channelCount; + outputSampleFormat = outputParameters->sampleFormat; + + /* all standard sample formats are supported by the buffer adapter, + * this implementation doesn't support any custom sample formats + */ + if( outputSampleFormat & paCustomFormat ) + { + return paSampleFormatNotSupported; + } + + /* unless alternate device specification is supported, reject the use of + * paUseHostApiSpecificDeviceSpecification + */ + if( outputParameters->device == paUseHostApiSpecificDeviceSpecification ) + { + return paInvalidDevice; + } + + /* check that output device can support outputChannelCount */ + if( outputChannelCount > + hostApi->deviceInfos[outputParameters->device]->maxOutputChannels ) + { + return paInvalidChannelCount; + } + + /* validate outputStreamInfo */ + if( outputParameters->hostApiSpecificStreamInfo ) + { + /* this implementation doesn't use custom stream info */ + return paIncompatibleHostApiSpecificStreamInfo; + } + + } + + else + { + outputChannelCount = 0; + } + + return paFormatIsSupported; +} + +/* Makes conversion from portaudio to pulseaudio sample defines + * Little endian formats are used (if there is some mystical big endian + * sound device this should be fixed but until then it's safe to believe + * this works + */ +PaError PaPulseAudio_ConvertPortaudioFormatToPaPulseAudio_( PaSampleFormat portaudiosf, + pa_sample_spec * pulseaudiosf ) +{ + switch( portaudiosf ) + { + case paFloat32: + pulseaudiosf->format = PA_SAMPLE_FLOAT32LE; + break; + + case paInt32: + pulseaudiosf->format = PA_SAMPLE_S32LE; + break; + + case paInt24: + pulseaudiosf->format = PA_SAMPLE_S24LE; + break; + + case paInt16: + pulseaudiosf->format = PA_SAMPLE_S16LE; + break; + + case paInt8: + pulseaudiosf->format = PA_SAMPLE_U8; + break; + + case paUInt8: + pulseaudiosf->format = PA_SAMPLE_U8; + break; + + case paCustomFormat: + case paNonInterleaved: + PA_DEBUG(("PaPulseAudio %s: THIS IS NOT SUPPORTED BY PULSEAUDIO!\n", + __FUNCTION__)); + return paSampleFormatNotSupported; + break; + } + + return paNoError; +} + + +/* Allocate buffer. */ +PaError PaPulseAudio_BlockingInitRingBuffer( PaUtilRingBuffer * rbuf, + int size ) +{ + char *ringbufferBuffer = (char *) malloc( size ); + PaError ret = paNoError; + + if( ringbufferBuffer == NULL ) + { + PA_PULSEAUDIO_SET_LAST_HOST_ERROR( 0, + "PaPulseAudio_BlockingInitRingBuffer: Not enough memory to handle request" ); + return paInsufficientMemory; + } + + memset( ringbufferBuffer, + 0x00, + size ); + + ret = PaUtil_InitializeRingBuffer( rbuf, + 1, + size, + ringbufferBuffer ); + + if( ret < paNoError ) + { + free( ringbufferBuffer ); + PA_DEBUG( ("Portaudio %s: Can't initialize input ringbuffer with size: %ld!\n", + __FUNCTION__, size) ); + PA_PULSEAUDIO_SET_LAST_HOST_ERROR( 0, + "PaPulseAudio_BlockingInitRingBuffer: Can't initialize input ringbuffer" ); + + return paNotInitialized; + } + + return paNoError; +} + +/* see pa_hostapi.h for a list of validity guarantees made about OpenStream parameters */ + +PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi, + PaStream ** s, + const PaStreamParameters * inputParameters, + const PaStreamParameters * outputParameters, + double sampleRate, + unsigned long framesPerBuffer, + PaStreamFlags streamFlags, + PaStreamCallback * streamCallback, + void *userData ) +{ + PaError result = paNoError; + PaPulseAudio_HostApiRepresentation *pulseaudioHostApi = + (PaPulseAudio_HostApiRepresentation *) hostApi; + PaPulseAudio_Stream *stream = NULL; + unsigned long framesPerHostBuffer = framesPerBuffer; /* these may not be equivalent for all implementations */ + int inputChannelCount, + outputChannelCount; + PaSampleFormat inputSampleFormat, + outputSampleFormat; + PaSampleFormat hostInputSampleFormat, + hostOutputSampleFormat; + + /* validate platform specific flags */ + if( (streamFlags & paPlatformSpecificFlags) != 0 ) + { + return paInvalidFlag; + } + + /* This is something that Pulseaudio can handle + * and it's also bearable small + */ + if( framesPerBuffer == paFramesPerBufferUnspecified ) + { + framesPerBuffer = PAPULSEAUDIO_FRAMESPERBUFFERUNSPEC; + } + + PaPulseAudio_Lock(pulseaudioHostApi->mainloop); + stream = + (PaPulseAudio_Stream *) PaUtil_AllocateZeroInitializedMemory( sizeof( PaPulseAudio_Stream ) ); + + if( !stream ) + { + result = paInsufficientMemory; + goto openstream_error; + } + + /* Allocate memory for source and sink names. */ + const char defaultSourceStreamName[] = "Portaudio source"; + const char defaultSinkStreamName[] = "Portaudio sink"; + + stream->framesPerHostCallback = framesPerBuffer; + stream->inputStreamName = (char*)PaUtil_AllocateZeroInitializedMemory( sizeof( defaultSourceStreamName ) ); + stream->outputStreamName = (char*)PaUtil_AllocateZeroInitializedMemory( sizeof( defaultSinkStreamName ) ); + if ( !stream->inputStreamName || !stream->outputStreamName ) + { + result = paInsufficientMemory; + goto openstream_error; + } + + /* Copy initial stream names to memory. */ + memcpy( stream->inputStreamName, defaultSourceStreamName, sizeof( defaultSourceStreamName ) ); + memcpy( stream->outputStreamName, defaultSinkStreamName, sizeof( defaultSinkStreamName ) ); + + stream->isActive = 0; + stream->isStopped = 1; + stream->pulseaudioIsActive = 0; + stream->pulseaudioIsStopped = 1; + + stream->inputStream = NULL; + stream->outputStream = NULL; + memset( &stream->inputRing, + 0x00, + sizeof( PaUtilRingBuffer ) ); + + if( inputParameters ) + { + inputChannelCount = inputParameters->channelCount; + inputSampleFormat = inputParameters->sampleFormat; + + /* unless alternate device specification is supported, reject the use of + * paUseHostApiSpecificDeviceSpecification + */ + if( inputParameters->device == paUseHostApiSpecificDeviceSpecification ) + { + result = paInvalidDevice; + goto openstream_error; + } + + /* check that input device can support inputChannelCount */ + if( inputChannelCount > + hostApi->deviceInfos[inputParameters->device]->maxInputChannels ) + { + result = paInvalidChannelCount; + goto openstream_error; + } + + /* validate inputStreamInfo */ + if( inputParameters->hostApiSpecificStreamInfo ) + { + /* this implementation doesn't use custom stream info */ + result = paIncompatibleHostApiSpecificStreamInfo; + goto openstream_error; + } + + hostInputSampleFormat = + PaUtil_SelectClosestAvailableFormat( inputSampleFormat, + inputSampleFormat ); + + stream->inputFrameSize = Pa_GetSampleSize( inputSampleFormat ) * inputChannelCount; + + result = PaPulseAudio_ConvertPortaudioFormatToPaPulseAudio_( + hostInputSampleFormat, + &stream->inputSampleSpec ); + + if( result != paNoError ) + { + goto openstream_error; + } + + stream->inputSampleSpec.rate = sampleRate; + stream->inputSampleSpec.channels = inputChannelCount; + stream->inputChannelCount = inputChannelCount; + + if( !pa_sample_spec_valid( &stream->inputSampleSpec ) ) + { + PA_DEBUG( ("Portaudio %s: Invalid input audio spec!\n", + __FUNCTION__) ); + result = paUnanticipatedHostError; /* should have been caught above */ + goto openstream_error; + } + + stream->inputStream = + pa_stream_new( pulseaudioHostApi->context, + stream->inputStreamName, + &stream->inputSampleSpec, + NULL ); + + if( stream->inputStream != NULL ) + { + pa_stream_set_state_callback( stream->inputStream, + PaPulseAudio_StreamStateCb, + stream); + pa_stream_set_started_callback( stream->inputStream, + PaPulseAudio_StreamStartedCb, + stream ); + } + else + { + PA_DEBUG( ("Portaudio %s: Can't alloc input stream!\n", + __FUNCTION__) ); + } + + stream->inputDevice = inputParameters->device; + + /* + * This is too much as most of the time there is not much + * stuff in buffer but it's enough if we are doing blocked + * and reading is somewhat slower than callback + */ + result = PaPulseAudio_BlockingInitRingBuffer( &stream->inputRing, + (65536 * 4) ); + if( result != paNoError ) + { + goto openstream_error; + } + + } + + else + { + inputChannelCount = 0; + inputSampleFormat = hostInputSampleFormat = paFloat32; + } + + if( outputParameters ) + { + outputChannelCount = outputParameters->channelCount; + outputSampleFormat = outputParameters->sampleFormat; + + /* unless alternate device specification is supported, reject the use of + * paUseHostApiSpecificDeviceSpecification + */ + if( outputParameters->device == paUseHostApiSpecificDeviceSpecification ) + { + result = paInvalidDevice; + goto openstream_error; + } + + /* check that output device can support inputChannelCount */ + if( outputChannelCount > + hostApi->deviceInfos[outputParameters->device]->maxOutputChannels ) + { + result = paInvalidChannelCount; + goto openstream_error; + } + + /* validate outputStreamInfo */ + if( outputParameters->hostApiSpecificStreamInfo ) + { + result = paIncompatibleHostApiSpecificStreamInfo; /* this implementation doesn't use custom stream info */ + goto openstream_error; + } + + /* IMPLEMENT ME - establish which host formats are available */ + hostOutputSampleFormat = + PaUtil_SelectClosestAvailableFormat( outputSampleFormat + /* native formats */ , + outputSampleFormat ); + + stream->outputFrameSize = + Pa_GetSampleSize( outputSampleFormat ) * outputChannelCount; + + result = PaPulseAudio_ConvertPortaudioFormatToPaPulseAudio_( + hostOutputSampleFormat, + &stream->outputSampleSpec ); + + if( result != paNoError ) + { + goto openstream_error; + } + + stream->outputSampleSpec.rate = sampleRate; + stream->outputSampleSpec.channels = outputChannelCount; + stream->outputChannelCount = outputChannelCount; + + if( !pa_sample_spec_valid( &stream->outputSampleSpec ) ) + { + PA_DEBUG( ("Portaudio %s: Invalid audio spec for output!\n", + __FUNCTION__) ); + result = paUnanticipatedHostError; /* should have been caught above */ + goto openstream_error; + } + + stream->outputStream = + pa_stream_new( pulseaudioHostApi->context, + stream->outputStreamName, + &stream->outputSampleSpec, + NULL ); + + if( stream->outputStream != NULL ) + { + pa_stream_set_state_callback( stream->outputStream, + PaPulseAudio_StreamStateCb, + stream ); + pa_stream_set_started_callback( stream->outputStream, + PaPulseAudio_StreamStartedCb, + stream ); + } + + else + { + PA_DEBUG( ("Portaudio %s: Can't alloc output stream!\n", + __FUNCTION__) ); + } + + if( result != paNoError ) + { + goto openstream_error; + } + + if( result != paNoError ) + { + goto openstream_error; + } + + stream->outputDevice = outputParameters->device; + } + + else + { + outputChannelCount = 0; + outputSampleFormat = hostOutputSampleFormat = paFloat32; + } + + stream->hostapi = pulseaudioHostApi; + stream->context = pulseaudioHostApi->context; + stream->mainloop = pulseaudioHostApi->mainloop; + + if( streamCallback ) + { + PaUtil_InitializeStreamRepresentation( &stream->streamRepresentation, + &pulseaudioHostApi->callbackStreamInterface, + streamCallback, + userData ); + } + else + { + PaUtil_InitializeStreamRepresentation( &stream->streamRepresentation, + &pulseaudioHostApi->blockingStreamInterface, + streamCallback, + userData ); + } + + PaUtil_InitializeCpuLoadMeasurer( &stream->cpuLoadMeasurer, + sampleRate + ); + + /* we assume a fixed host buffer size in this example, but the buffer processor + * can also support bounded and unknown host buffer sizes by passing + * paUtilBoundedHostBufferSize or paUtilUnknownHostBufferSize instead of + * paUtilFixedHostBufferSize below. */ + + result = PaUtil_InitializeBufferProcessor( &stream->bufferProcessor, + inputChannelCount, + inputSampleFormat, + hostInputSampleFormat, + outputChannelCount, + outputSampleFormat, + hostOutputSampleFormat, + sampleRate, + streamFlags, + framesPerBuffer, + framesPerHostBuffer, + paUtilUnknownHostBufferSize, + streamCallback, + userData ); + + if( result != paNoError ) + { + goto openstream_error; + } + + /* inputLatency is specified in _seconds_ */ + stream->streamRepresentation.streamInfo.inputLatency = + (PaTime) PaUtil_GetBufferProcessorInputLatencyFrames( + &stream->bufferProcessor ) / sampleRate; + + /* outputLatency is specified in _seconds_ */ + stream->streamRepresentation.streamInfo.outputLatency = + (PaTime) PaUtil_GetBufferProcessorOutputLatencyFrames( + &stream->bufferProcessor ) / sampleRate; + + stream->streamRepresentation.streamInfo.sampleRate = sampleRate; + + stream->maxFramesHostPerBuffer = framesPerBuffer; + stream->maxFramesPerBuffer = framesPerBuffer; + + *s = (PaStream *) stream; + + openstream_end: + PaPulseAudio_UnLock( pulseaudioHostApi->mainloop ); + return result; + + openstream_error: + + if( stream ) + { + PaUtil_FreeMemory( stream->inputStreamName ); + PaUtil_FreeMemory( stream->outputStreamName ); + PaUtil_FreeMemory( stream ); + } + + goto openstream_end; +} + +PaError IsStreamStopped( PaStream * s ) +{ + PaPulseAudio_Stream *stream = (PaPulseAudio_Stream *) s; + return stream->isStopped; +} + + +PaError IsStreamActive( PaStream * s ) +{ + PaPulseAudio_Stream *stream = (PaPulseAudio_Stream *) s; + return stream->isActive; +} + + +PaTime GetStreamTime( PaStream * s ) +{ + PaPulseAudio_Stream *stream = (PaPulseAudio_Stream *) s; + PaPulseAudio_HostApiRepresentation *pulseaudioHostApi = stream->hostapi; + PaStreamCallbackTimeInfo timeInfo = { 0, 0, 0 }; + + PaPulseAudio_Lock( pulseaudioHostApi->mainloop ); + + if( stream->outputStream ) + { + if( PaPulseAudio_updateTimeInfo( stream->outputStream, + &timeInfo, + 0 ) == -PA_ERR_NODATA ) + { + return 0; + } + } + + if( stream->inputStream ) + { + if( PaPulseAudio_updateTimeInfo( stream->inputStream, + &timeInfo, + 1 ) == -PA_ERR_NODATA ) + { + return 0; + } + } + + PaPulseAudio_UnLock( pulseaudioHostApi->mainloop ); + return timeInfo.currentTime; +} + + +double GetStreamCpuLoad( PaStream * s ) +{ + PaPulseAudio_Stream *stream = (PaPulseAudio_Stream *) s; + + return PaUtil_GetCpuLoad( &stream->cpuLoadMeasurer ); +} + +/** Extensions */ +static void RenameStreamCb(pa_stream *s, int success, void *userdata) +{ + /* Currently does nothing but signal the caller. */ + PaPulseAudio_Stream *l_ptrStream = (PaPulseAudio_Stream *) userdata; + pa_threaded_mainloop_signal( l_ptrStream->mainloop, + 0 ); +} + +PaError PaPulseAudio_RenameSource( PaStream *s, const char *streamName ) +{ + PaPulseAudio_Stream *stream = (PaPulseAudio_Stream *) s; + PaError result = paNoError; + pa_operation *op = NULL; + + if ( stream->inputStream == NULL ) + { + return paInvalidDevice; + } + + /* Reallocate stream name in memory. */ + PaPulseAudio_Lock( stream->mainloop ); + char *newStreamName = (char*)PaUtil_AllocateZeroInitializedMemory( strnlen( streamName, PAPULSEAUDIO_MAX_DEVICENAME ) + 1 ); + if ( !newStreamName ) + { + PaPulseAudio_UnLock( stream->mainloop ); + return paInsufficientMemory; + } + snprintf( newStreamName, strnlen( streamName, PAPULSEAUDIO_MAX_DEVICENAME ) + 1, "%s", streamName ); + + PaUtil_FreeMemory( stream->inputStreamName ); + stream->inputStreamName = newStreamName; + + op = pa_stream_set_name( stream->inputStream, streamName, RenameStreamCb, stream ); + PaPulseAudio_UnLock( stream->mainloop ); + + /* Wait for completion. */ + while (pa_operation_get_state( op ) == PA_OPERATION_RUNNING) + { + pa_threaded_mainloop_wait( stream->mainloop ); + } + + return result; +} + +PaError PaPulseAudio_RenameSink( PaStream *s, const char *streamName ) +{ + PaPulseAudio_Stream *stream = (PaPulseAudio_Stream *) s; + PaError result = paNoError; + pa_operation *op = NULL; + + if ( stream->outputStream == NULL ) + { + return paInvalidDevice; + } + + /* Reallocate stream name in memory. */ + PaPulseAudio_Lock( stream->mainloop ); + char *newStreamName = (char*)PaUtil_AllocateZeroInitializedMemory( strnlen( streamName, PAPULSEAUDIO_MAX_DEVICENAME ) + 1 ); + if ( !newStreamName ) + { + PaPulseAudio_UnLock( stream->mainloop ); + return paInsufficientMemory; + } + snprintf( newStreamName, strnlen( streamName, PAPULSEAUDIO_MAX_DEVICENAME ) + 1, "%s", streamName ); + + PaUtil_FreeMemory( stream->outputStreamName ); + stream->outputStreamName = newStreamName; + + op = pa_stream_set_name( stream->outputStream, streamName, RenameStreamCb, stream ); + PaPulseAudio_UnLock( stream->mainloop ); + + /* Wait for completion. */ + while (pa_operation_get_state( op ) == PA_OPERATION_RUNNING) + { + pa_threaded_mainloop_wait( stream->mainloop ); + } + + return result; +} diff --git a/3rdparty/portaudio/src/hostapi/pulseaudio/pa_linux_pulseaudio_block.c b/3rdparty/portaudio/src/hostapi/pulseaudio/pa_linux_pulseaudio_block.c new file mode 100644 index 0000000000000..3ef9b458c87fc --- /dev/null +++ b/3rdparty/portaudio/src/hostapi/pulseaudio/pa_linux_pulseaudio_block.c @@ -0,0 +1,197 @@ + +/* + * PulseAudio host to play natively in Linux based systems without + * ALSA emulation + * + * Copyright (c) 2014-2023 Tuukka Pasanen + * Copyright (c) 2016 Sqweek + * + * Based on the Open Source API proposed by Ross Bencina + * Copyright (c) 1999-2002 Ross Bencina, Phil Burk + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/* + * The text above constitutes the entire PortAudio license; however, + * the PortAudio community also makes the following non-binding requests: + * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. It is also + * requested that these non-binding requests be included along with the + * license above. + */ + +/** @file + @ingroup common_src + + @brief PulseAudio implementation of support for a host API. + + This host API implements PulseAudio support for portaudio + it has callbackmode and normal write mode support +*/ + +#include "pa_linux_pulseaudio_block_internal.h" +#include + +/* + As separate stream interfaces are used for blocking and callback + streams, the following functions can be guaranteed to only be called + for blocking streams. +*/ + +PaError PaPulseAudio_ReadStreamBlock( PaStream * s, + void *buffer, + unsigned long frames ) +{ + PaPulseAudio_Stream *pulseaudioStream = (PaPulseAudio_Stream *) s; + PaPulseAudio_HostApiRepresentation *pulseaudioHostApi = + pulseaudioStream->hostapi; + PaError ret = 0; + uint8_t *readableBuffer = (uint8_t *) buffer; + long bufferLeftToRead = (frames * pulseaudioStream->inputFrameSize); + + while( bufferLeftToRead > 0 ) + { + PA_PULSEAUDIO_IS_ERROR( pulseaudioStream, paStreamIsStopped ) + + PaPulseAudio_Lock( pulseaudioStream->mainloop ); + long l_read = PaUtil_ReadRingBuffer( &pulseaudioStream->inputRing, readableBuffer, + bufferLeftToRead ); + readableBuffer += l_read; + bufferLeftToRead -= l_read; + if( bufferLeftToRead > 0 ) + pa_threaded_mainloop_wait( pulseaudioStream->mainloop ); + + PaPulseAudio_UnLock( pulseaudioStream->mainloop ); + + if( bufferLeftToRead > 0 ) + { + /* Sleep small amount of time not burn CPU + * we block anyway so this is bearable + */ + usleep(100); + } + } + return paNoError; +} + + +PaError PaPulseAudio_WriteStreamBlock( PaStream * s, + const void *buffer, + unsigned long frames ) +{ + PaPulseAudio_Stream *pulseaudioStream = (PaPulseAudio_Stream *) s; + int ret = 0; + size_t pulseaudioWritable = 0; + uint8_t *writableBuffer = (uint8_t *) buffer; + long bufferLeftToWrite = (frames * pulseaudioStream->outputFrameSize); + pa_operation *pulseaudioOperation = NULL; + + PaUtil_BeginCpuLoadMeasurement( &pulseaudioStream->cpuLoadMeasurer ); + + while( bufferLeftToWrite > 0) + { + PA_PULSEAUDIO_IS_ERROR( pulseaudioStream, paStreamIsStopped ) + + PaPulseAudio_Lock( pulseaudioStream->mainloop ); + pulseaudioWritable = pa_stream_writable_size( pulseaudioStream->outputStream ); + PaPulseAudio_UnLock( pulseaudioStream->mainloop ); + + if( pulseaudioWritable > 0 ) + { + if( bufferLeftToWrite < pulseaudioWritable ) + { + pulseaudioWritable = bufferLeftToWrite; + } + PaPulseAudio_Lock( pulseaudioStream->mainloop ); + ret = pa_stream_write( pulseaudioStream->outputStream, + writableBuffer, + pulseaudioWritable, + NULL, + 0, + PA_SEEK_RELATIVE ); + + pulseaudioOperation = pa_stream_update_timing_info( pulseaudioStream->outputStream, + NULL, + NULL ); + PaPulseAudio_UnLock( pulseaudioStream->mainloop ); + + ret = 0; + + if( pulseaudioOperation == NULL ) + { + return paInsufficientMemory; + } + + while( pa_operation_get_state( pulseaudioOperation ) == PA_OPERATION_RUNNING ) + { + ret ++; + PA_PULSEAUDIO_IS_ERROR( pulseaudioStream, paStreamIsStopped ) + + /* As this shouldn never happen it's error if it does */ + if( ret >= 10000 ) + { + return paStreamIsStopped; + } + + usleep(100); + } + + PaPulseAudio_Lock( pulseaudioStream->mainloop ); + + pa_operation_unref( pulseaudioOperation ); + pulseaudioOperation = NULL; + + PaPulseAudio_UnLock( pulseaudioStream->mainloop ); + + writableBuffer += pulseaudioWritable; + bufferLeftToWrite -= pulseaudioWritable; + } + + if( bufferLeftToWrite > 0 ) + { + /* Sleep small amount of time not burn CPU + * we block anyway so this is bearable + */ + usleep(100); + } + + } + PaUtil_EndCpuLoadMeasurement( &pulseaudioStream->cpuLoadMeasurer, + frames ); + + return paNoError; +} + + +signed long PaPulseAudio_GetStreamReadAvailableBlock( PaStream * s ) +{ + PaPulseAudio_Stream *pulseaudioStream = (PaPulseAudio_Stream *) s; + + if( pulseaudioStream->inputStream == NULL ) + { + return 0; + } + + return ( PaUtil_GetRingBufferReadAvailable( &pulseaudioStream->inputRing ) / + pulseaudioStream->inputFrameSize ); +} diff --git a/3rdparty/portaudio/src/hostapi/pulseaudio/pa_linux_pulseaudio_block_internal.h b/3rdparty/portaudio/src/hostapi/pulseaudio/pa_linux_pulseaudio_block_internal.h new file mode 100644 index 0000000000000..1c1bb092ca804 --- /dev/null +++ b/3rdparty/portaudio/src/hostapi/pulseaudio/pa_linux_pulseaudio_block_internal.h @@ -0,0 +1,91 @@ + +/* + * PulseAudio host to play natively in Linux based systems without + * ALSA emulation + * + * Copyright (c) 2014-2023 Tuukka Pasanen + * Copyright (c) 2016 Sqweek + * + * Based on the Open Source API proposed by Ross Bencina + * Copyright (c) 1999-2002 Ross Bencina, Phil Burk + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/* + * The text above constitutes the entire PortAudio license; however, + * the PortAudio community also makes the following non-binding requests: + * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. It is also + * requested that these non-binding requests be included along with the + * license above. + */ + +#ifndef _PA_HOSTAPI_PULSEAUDIO_BLOCK_H_ +#define _PA_HOSTAPI_PULSEAUDIO_BLOCK_H_ + +#include "pa_util.h" +#include "pa_allocation.h" +#include "pa_hostapi.h" +#include "pa_stream.h" +#include "pa_cpuload.h" +#include "pa_process.h" + +#include "pa_unix_util.h" +#include "pa_ringbuffer.h" + +/* PulseAudio headers */ +#include +#include +#include + +#include "pa_linux_pulseaudio_internal.h" + +#ifdef __cplusplus +extern "C" +{ +#endif /* __cplusplus */ + +PaError PaPulseAudio_CloseStreamBlock( PaStream * stream ); + +PaError PaPulseAudio_StartStreamBlock( PaStream * stream ); + +PaError PaPulseAudio_StopStreamBlock( PaStream * stream ); + +PaError PaPulseAudio_AbortStreamBlock( PaStream * stream ); + +PaError PaPulseAudio_ReadStreamBlock( PaStream * stream, + void *buffer, + unsigned long frames ); + +PaError PaPulseAudio_WriteStreamBlock( PaStream * stream, + const void *buffer, + unsigned long frames ); + +signed long PaPulseAudio_GetStreamReadAvailableBlock( PaStream * stream ); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + + +#endif diff --git a/3rdparty/portaudio/src/hostapi/pulseaudio/pa_linux_pulseaudio_cb.c b/3rdparty/portaudio/src/hostapi/pulseaudio/pa_linux_pulseaudio_cb.c new file mode 100644 index 0000000000000..18da322898262 --- /dev/null +++ b/3rdparty/portaudio/src/hostapi/pulseaudio/pa_linux_pulseaudio_cb.c @@ -0,0 +1,971 @@ + +/* + * PulseAudio host to play natively in Linux based systems without + * ALSA emulation + * + * Copyright (c) 2014-2023 Tuukka Pasanen + * Copyright (c) 2016 Sqweek + * + * Based on the Open Source API proposed by Ross Bencina + * Copyright (c) 1999-2002 Ross Bencina, Phil Burk + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/* + * The text above constitutes the entire PortAudio license; however, + * the PortAudio community also makes the following non-binding requests: + * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. It is also + * requested that these non-binding requests be included along with the + * license above. + */ + +/** @file + @ingroup common_src + + @brief PulseAudio implementation of support for a host API. + + This host API implements PulseAudio support for portaudio + it has callback mode and normal write mode support +*/ + + +#include "pa_util.h" +#include "pa_allocation.h" +#include "pa_hostapi.h" +#include "pa_stream.h" +#include "pa_cpuload.h" +#include "pa_process.h" + +#include "pa_unix_util.h" +#include "pa_ringbuffer.h" + +#include "pa_linux_pulseaudio_cb_internal.h" + + +/* PulseAudio headers */ +#include +#include + +int PaPulseAudio_updateTimeInfo( pa_stream * s, + PaStreamCallbackTimeInfo *timeInfo, + int record ) +{ + unsigned int isNegative = 0; + pa_usec_t pulseaudioStreamTime = 0; + pa_usec_t pulseaudioStreamLatency = 0; + + if( pa_stream_get_time( s, + &pulseaudioStreamTime ) == -PA_ERR_NODATA ) + { + return -PA_ERR_NODATA; + } + else + { + timeInfo->currentTime = ((PaTime) pulseaudioStreamTime / (PaTime) 1000000); + } + + if( pa_stream_get_latency( s, + &pulseaudioStreamLatency, + &isNegative ) == -PA_ERR_NODATA ) + { + return -PA_ERR_NODATA; + } + else + { + if( record == 0 ) + { + timeInfo->outputBufferDacTime = timeInfo->currentTime + ((PaTime) pulseaudioStreamLatency / (PaTime) 1000000); + } + else + { + timeInfo->inputBufferAdcTime = timeInfo->currentTime - ((PaTime) pulseaudioStreamLatency / (PaTime) 1000000); + } + } + return 0; +} + + +/* locks the Pulse Main loop when not called from it */ +void PaPulseAudio_Lock( pa_threaded_mainloop *mainloop ) +{ + if( !pa_threaded_mainloop_in_thread( mainloop ) ) { + pa_threaded_mainloop_lock( mainloop ); + } +} + +/* unlocks the Pulse Main loop when not called from it */ +void PaPulseAudio_UnLock( pa_threaded_mainloop *mainloop ) +{ + if( !pa_threaded_mainloop_in_thread( mainloop ) ) { + pa_threaded_mainloop_unlock( mainloop ); + } +} + +void _PaPulseAudio_WriteRingBuffer( PaUtilRingBuffer *ringbuffer, + const void *buffer, + size_t length ) +{ + /* + * If there is not enough room. Read from ringbuffer to make + * sure that if not full and audio will just underrun + * + * If you try to read too much and there is no room then this + * will fail. But I don't know how to get into that? + */ + if( PaUtil_GetRingBufferWriteAvailable( ringbuffer ) < length ) + { + uint8_t tmpBuffer[ PULSEAUDIO_BUFFER_SIZE ]; + PaUtil_ReadRingBuffer( ringbuffer, + tmpBuffer, + length ); + } + + PaUtil_WriteRingBuffer( ringbuffer, + buffer, + length ); + +} + +void _PaPulseAudio_Read( PaPulseAudio_Stream *stream, + size_t length ) +{ + const void *pulseaudioData = NULL; + + /* + * Idea behind this is that we fill ringbuffer with data + * that comes from input device. When it's available + * we'll fill it to callback or blocking reading + */ + if( pa_stream_peek( stream->inputStream, + &pulseaudioData, + &length )) + { + PA_DEBUG( ("Portaudio %s: Can't read audio!\n", + __FUNCTION__) ); + } + else + { + _PaPulseAudio_WriteRingBuffer( &stream->inputRing, pulseaudioData, length ); + } + + pa_stream_drop( stream->inputStream ); + + pulseaudioData = NULL; + +} + +static int _PaPulseAudio_ProcessAudio(PaPulseAudio_Stream *stream, + size_t length) +{ + uint8_t pulseaudioSampleBuffer[PULSEAUDIO_BUFFER_SIZE]; + size_t hostFramesPerBuffer = stream->bufferProcessor.framesPerHostBuffer; + size_t pulseaudioOutputBytes = 0; + size_t pulseaudioInputBytes = 0; + size_t hostFrameCount = 0; + int isOutputCb = 0; + int isInputCb = 0; + PaStreamCallbackTimeInfo timeInfo; + int ret = paContinue; + void *bufferData = NULL; + size_t pulseaudioOutputWritten = 0; + + /* If there is no specified per host buffer then + * just generate one or but correct one in place + */ + if( hostFramesPerBuffer == paFramesPerBufferUnspecified ) + { + if( !stream->framesPerHostCallback ) + { + /* This just good enough and most + * Pulseaudio server and ALSA can handle it + * + * We should never get here but this is ultimate + * backup. + */ + hostFramesPerBuffer = PAPULSEAUDIO_FRAMESPERBUFFERUNSPEC; + + stream->framesPerHostCallback = hostFramesPerBuffer; + } + else + { + hostFramesPerBuffer = stream->framesPerHostCallback; + } + } + + if( stream->outputStream ) + { + /* Calculate how many bytes goes to one frame */ + pulseaudioInputBytes = pulseaudioOutputBytes = (hostFramesPerBuffer * stream->outputFrameSize); + + if( stream->bufferProcessor.streamCallback ) + { + isOutputCb = 1; + } + } + + /* If we just want to have input but not output (Not Duplex) + * Use this calculation + */ + if( stream->inputStream ) + { + pulseaudioInputBytes = pulseaudioOutputBytes = (hostFramesPerBuffer * stream->inputFrameSize); + + if( stream->bufferProcessor.streamCallback ) + { + isInputCb = 1; + } + } + + /* + * When stopped we should stop feeding or recording right away + */ + if( stream->isStopped ) + { + return paStreamIsStopped; + } + + /* + * This can be called before we have reached out + * starting Portaudio stream or Portaudio stream + * is stopped + */ + if( !stream->pulseaudioIsActive ) + { + if(stream->outputStream) + { + bufferData = pulseaudioSampleBuffer; + memset( bufferData, 0x00, length); + + pa_stream_write( stream->outputStream, + bufferData, + length, + NULL, + 0, + PA_SEEK_RELATIVE ); + } + + return paContinue; + } + + /* If we have input which is mono and + * output which is stereo. We have to copy + * mono to monomono which is stereo. + * Then just read half and copy + */ + if( isOutputCb && + stream->outputSampleSpec.channels == 2 && + stream->inputSampleSpec.channels == 1) + { + pulseaudioInputBytes /= 2; + } + + while(1) + { + /* + * If everything fail like stream vanish or mainloop + * is in error state try to handle error + */ + PA_PULSEAUDIO_IS_ERROR( stream, paStreamIsStopped ) + + /* There is only Record stream so + * see if we have enough stuff to feed record stream + * If not then bail out. + */ + if( isInputCb && + PaUtil_GetRingBufferReadAvailable(&stream->inputRing) < pulseaudioInputBytes ) + { + if(isOutputCb && (pulseaudioOutputWritten < length) && !stream->missedBytes) + { + stream->missedBytes = length - pulseaudioOutputWritten; + } + else + { + stream->missedBytes = 0; + } + break; + } + else if( pulseaudioOutputWritten >= length) + { + stream->missedBytes = 0; + break; + } + + if( stream->outputStream ) + { + PaPulseAudio_updateTimeInfo( stream->outputStream, + &timeInfo, + 0 ); + } + + if( stream->inputStream ) + { + PaPulseAudio_updateTimeInfo( stream->inputStream, + &timeInfo, + 1 ); + } + + PaUtil_BeginCpuLoadMeasurement( &stream->cpuLoadMeasurer ); + + /* When doing Portaudio Duplex one has to write and read same amount of data + * if not done that way Portaudio will go boo boo and nothing works. + * This is why this is done as it's done + * + * Pulseaudio does not care and this is something that needs small adjusting + */ + PaUtil_BeginBufferProcessing( &stream->bufferProcessor, + &timeInfo, + 0 ); + + /* Read of ther is something to read */ + if( isInputCb ) + { + PaUtil_ReadRingBuffer( &stream->inputRing, + pulseaudioSampleBuffer, + pulseaudioInputBytes); + + PaUtil_SetInterleavedInputChannels( &stream->bufferProcessor, + 0, + pulseaudioSampleBuffer, + stream->inputSampleSpec.channels ); + + PaUtil_SetInputFrameCount( &stream->bufferProcessor, + hostFramesPerBuffer ); + } + + if( isOutputCb ) + { + + size_t tmpSize = pulseaudioOutputBytes; + + /* Allocate memory to make it faster to output stuff */ + pa_stream_begin_write( stream->outputStream, &bufferData, &tmpSize ); + + /* If bufferData is NULL then output is not ready + * and we have to wait for it + */ + if(!bufferData) + { + return paNotInitialized; + } + + PaUtil_SetInterleavedOutputChannels( &stream->bufferProcessor, + 0, + bufferData, + stream->outputChannelCount ); + + PaUtil_SetOutputFrameCount( &stream->bufferProcessor, + hostFramesPerBuffer ); + + if( pa_stream_write( stream->outputStream, + bufferData, + pulseaudioOutputBytes, + NULL, + 0, + PA_SEEK_RELATIVE ) ) + { + PA_DEBUG( ("Portaudio %s: Can't write audio!\n", + __FUNCTION__) ); + } + + pulseaudioOutputWritten += pulseaudioOutputBytes; + } + + hostFrameCount = + PaUtil_EndBufferProcessing( &stream->bufferProcessor, + &ret ); + + PaUtil_EndCpuLoadMeasurement( &stream->cpuLoadMeasurer, + hostFrameCount ); + } + + return ret; +} + +void PaPulseAudio_StreamRecordCb( pa_stream * s, + size_t length, + void *userdata ) +{ + PaPulseAudio_Stream *pulseaudioStream = (PaPulseAudio_Stream *) userdata; + + if( !pulseaudioStream->pulseaudioIsActive ) + { + pulseaudioStream->pulseaudioIsActive = 1; + pulseaudioStream->pulseaudioIsStopped= 0; + } + + _PaPulseAudio_Read( pulseaudioStream, length ); + + /* Let's handle when output happens if Duplex + * + * Also there is no callback there is no meaning to continue + * as we have blocking reading + */ + if( pulseaudioStream->bufferProcessor.streamCallback ) + { + _PaPulseAudio_ProcessAudio( pulseaudioStream, length ); + } + + pa_threaded_mainloop_signal( pulseaudioStream->mainloop, + 0 ); +} + +void PaPulseAudio_StreamPlaybackCb( pa_stream * s, + size_t length, + void *userdata ) +{ + PaPulseAudio_Stream *pulseaudioStream = (PaPulseAudio_Stream *) userdata; + + if( !pulseaudioStream->inputStream && !pulseaudioStream->pulseaudioIsActive ) + { + pulseaudioStream->pulseaudioIsActive = 1; + pulseaudioStream->pulseaudioIsStopped = 0; + } + + if( pulseaudioStream->bufferProcessor.streamCallback ) + { + _PaPulseAudio_ProcessAudio( pulseaudioStream, length ); + } + + pa_threaded_mainloop_signal( pulseaudioStream->mainloop, + 0 ); +} + +/* This is left for future use! */ +static void PaPulseAudio_StreamSuccessCb( pa_stream * s, + int success, + void *userdata ) +{ + PaPulseAudio_Stream *pulseaudioStream = (PaPulseAudio_Stream *) userdata; + PA_DEBUG( ("Portaudio %s: %d\n", __FUNCTION__, + success) ); + pa_threaded_mainloop_signal( pulseaudioStream->mainloop, + 0 ); +} + +/* This is left for future use! */ +static void PaPulseAudio_CorkSuccessCb( + pa_stream * s, + int success, + void *userdata +) +{ + PaPulseAudio_Stream *pulseaudioStream = (PaPulseAudio_Stream *) userdata; + pa_threaded_mainloop_signal( pulseaudioStream->mainloop, + 0 ); +} + + +/* This is left for future use! */ +void PaPulseAudio_StreamStartedCb( pa_stream * stream, + void *userdata ) +{ + PaPulseAudio_Stream *pulseaudioStream = (PaPulseAudio_Stream *) userdata; + pa_threaded_mainloop_signal( pulseaudioStream->mainloop, + 0 ); +} + +/* + When CloseStream() is called, the multi-api layer ensures that + the stream has already been stopped or aborted. +*/ +PaError PaPulseAudio_CloseStreamCb( PaStream * s ) +{ + PaError result = paNoError; + PaPulseAudio_Stream *stream = (PaPulseAudio_Stream *) s; + PaPulseAudio_HostApiRepresentation *pulseaudioHostApi = stream->hostapi; + pa_operation *pulseaudioOperation = NULL; + int waitLoop = 0; + int pulseaudioError = 0; + + /* Wait for stream to be stopped */ + stream->isActive = 0; + stream->isStopped = 1; + stream->pulseaudioIsActive = 0; + stream->pulseaudioIsStopped = 1; + + if( stream->outputStream != NULL + && pa_stream_get_state( stream->outputStream ) == PA_STREAM_READY ) + { + PaPulseAudio_Lock(stream->mainloop); + /** + * Pause stream so it stops faster + */ + pulseaudioOperation = pa_stream_cork( stream->outputStream, + 1, + PaPulseAudio_CorkSuccessCb, + stream ); + + PaPulseAudio_UnLock( stream->mainloop ); + + while( pa_operation_get_state( pulseaudioOperation ) == PA_OPERATION_RUNNING ) + { + pa_threaded_mainloop_wait( pulseaudioHostApi->mainloop ); + waitLoop ++; + + if(waitLoop > 250) + { + break; + } + } + + waitLoop = 0; + + PaPulseAudio_Lock(stream->mainloop); + pa_operation_unref( pulseaudioOperation ); + pulseaudioOperation = NULL; + + pa_stream_disconnect( stream->outputStream ); + PaPulseAudio_UnLock( stream->mainloop ); + } + + if( stream->inputStream != NULL + && pa_stream_get_state( stream->inputStream ) == PA_STREAM_READY ) + { + PaPulseAudio_Lock( stream->mainloop ); + + /** + * Pause stream so it stops so it stops faster + */ + pulseaudioOperation = pa_stream_cork( stream->inputStream, + 1, + PaPulseAudio_CorkSuccessCb, + stream ); + + PaPulseAudio_UnLock( stream->mainloop ); + + while( pa_operation_get_state( pulseaudioOperation ) == PA_OPERATION_RUNNING ) + { + pa_threaded_mainloop_wait( pulseaudioHostApi->mainloop ); + waitLoop ++; + + if(waitLoop > 250) + { + break; + } + } + + waitLoop = 0; + + PaPulseAudio_Lock( stream->mainloop ); + pa_operation_unref( pulseaudioOperation ); + pulseaudioOperation = NULL; + + /* Then we disconnect stream and wait for + * Termination + */ + pa_stream_disconnect( stream->inputStream ); + + PaPulseAudio_UnLock( stream->mainloop ); + + } + + /* Wait for termination for both */ + while(!waitLoop) + { + PaPulseAudio_Lock( stream->mainloop ); + if( stream->inputStream != NULL + && pa_stream_get_state( stream->inputStream ) == PA_STREAM_TERMINATED ) + { + pa_stream_unref( stream->inputStream ); + stream->inputStream = NULL; + } + PaPulseAudio_UnLock( stream->mainloop ); + + PaPulseAudio_Lock( stream->mainloop ); + if( stream->outputStream != NULL + && pa_stream_get_state(stream->outputStream) == PA_STREAM_TERMINATED ) + { + pa_stream_unref( stream->outputStream ); + stream->outputStream = NULL; + } + PaPulseAudio_UnLock( stream->mainloop ); + + if((stream->outputStream == NULL + && stream->inputStream == NULL) + || pulseaudioError >= 5000 ) + { + waitLoop = 1; + } + + pulseaudioError ++; + usleep(10000); + } + + PaUtil_TerminateBufferProcessor( &stream->bufferProcessor ); + PaUtil_TerminateStreamRepresentation( &stream->streamRepresentation ); + + PaUtil_FreeMemory( stream->inputStreamName ); + PaUtil_FreeMemory( stream->outputStreamName ); + PaUtil_FreeMemory( stream ); + + return result; +} + +PaError PaPulseAudio_StartStreamCb( PaStream * s ) +{ + PaError ret = paNoError; + PaPulseAudio_Stream *stream = (PaPulseAudio_Stream *) s; + int pulseaudioPlaybackStarted = 0; + int pulseaudioRecordStarted = 0; + pa_stream_state_t pulseaudioState = PA_STREAM_UNCONNECTED; + PaPulseAudio_HostApiRepresentation *pulseaudioHostApi = stream->hostapi; + const char *pulseaudioName = NULL; + pa_operation *pulseaudioOperation = NULL; + int waitLoop = 0; + unsigned int pulseaudioReqFrameSize = (1024 * 2); + + stream->isActive = 0; + stream->isStopped = 1; + stream->pulseaudioIsActive = 0; + stream->pulseaudioIsStopped = 1; + stream->missedBytes = 0; + + /* Ready the processor */ + PaUtil_ResetBufferProcessor( &stream->bufferProcessor ); + + PaPulseAudio_Lock( pulseaudioHostApi->mainloop ); + /* Adjust latencies if that is wanted + * https://www.freedesktop.org/wiki/Software/PulseAudio/Documentation/Developer/Clients/LatencyControl/ + * + * tlength is for Playback + * fragsize if for Record + */ + stream->outputBufferAttr.maxlength = (uint32_t)-1; + stream->inputBufferAttr.maxlength = (uint32_t)-1; + + stream->outputBufferAttr.tlength = (uint32_t)-1; + stream->inputBufferAttr.tlength = (uint32_t)-1; + + stream->outputBufferAttr.fragsize = (uint32_t)-1; + stream->inputBufferAttr.fragsize = (uint32_t)-1; + + stream->outputBufferAttr.prebuf = (uint32_t)-1; + stream->inputBufferAttr.prebuf = (uint32_t)-1; + + stream->outputBufferAttr.minreq = (uint32_t)-1; + stream->inputBufferAttr.minreq = (uint32_t)-1; + + stream->outputUnderflows = 0; + PaPulseAudio_UnLock( pulseaudioHostApi->mainloop ); + + pa_stream_flags_t pulseaudioStreamFlags = PA_STREAM_INTERPOLATE_TIMING | + PA_STREAM_AUTO_TIMING_UPDATE | + PA_STREAM_ADJUST_LATENCY | + PA_STREAM_NO_REMIX_CHANNELS | + PA_STREAM_NO_REMAP_CHANNELS | + PA_STREAM_DONT_MOVE; + + if( stream->inputStream ) + { + /* Default input reads 65,535 bytes setting fragsize + * fragments request to smaller chunks of data so it's + * easier to get nicer looking timestamps with current + * callback system + */ + stream->inputBufferAttr.fragsize = pa_usec_to_bytes( pulseaudioReqFrameSize, + &stream->inputSampleSpec ); + + if( stream->inputDevice != paNoDevice) + { + PA_DEBUG( ("Portaudio %s: %d (%s)\n", __FUNCTION__, stream->inputDevice, + pulseaudioHostApi->pulseaudioDeviceNames[stream-> + inputDevice]) ); + } + + pa_stream_set_read_callback( stream->inputStream, + PaPulseAudio_StreamRecordCb, + stream ); + + PaDeviceIndex defaultInputDevice; + PaError result = PaUtil_DeviceIndexToHostApiDeviceIndex( + &defaultInputDevice, + pulseaudioHostApi->inheritedHostApiRep.info.defaultInputDevice, + &(pulseaudioHostApi->inheritedHostApiRep) ); + + /* NULL means default device */ + pulseaudioName = NULL; + + /* If default device is not requested then change to wanted device */ + if( result == paNoError && stream->inputDevice != defaultInputDevice ) + { + pulseaudioName = pulseaudioHostApi-> + pulseaudioDeviceNames[stream->inputDevice]; + } + + if ( result == paNoError ) + { + PaPulseAudio_Lock( pulseaudioHostApi->mainloop ); + /* Zero means success */ + if( ! pa_stream_connect_record( stream->inputStream, + pulseaudioName, + &stream->inputBufferAttr, + pulseaudioStreamFlags ) ) + { + pa_stream_set_started_callback( stream->inputStream, + PaPulseAudio_StreamStartedCb, + stream ); + } + else + { + PA_DEBUG( ("Portaudio %s: Can't read audio!\n", + __FUNCTION__) ); + + goto startstreamcb_error; + } + PaPulseAudio_UnLock( pulseaudioHostApi->mainloop ); + + for( waitLoop = 0; waitLoop < 100; waitLoop ++ ) + { + PaPulseAudio_Lock( pulseaudioHostApi->mainloop ); + pulseaudioState = pa_stream_get_state( stream->inputStream ); + PaPulseAudio_UnLock( pulseaudioHostApi->mainloop ); + + if( pulseaudioState == PA_STREAM_READY ) + { + break; + } + else if( pulseaudioState == PA_STREAM_FAILED || + pulseaudioState == PA_STREAM_TERMINATED ) + { + goto startstreamcb_error; + } + + usleep(10000); + } + } + else + { + goto startstreamcb_error; + } + + } + + if( stream->outputStream ) + { + /* tlength does almost the same as fragsize in record. + * See reasoning up there in comments. + * + * In future this should we tuned when things changed + * this just 'good' default + */ + stream->outputBufferAttr.tlength = pa_usec_to_bytes( pulseaudioReqFrameSize, + &stream->outputSampleSpec ); + + pa_stream_set_write_callback( stream->outputStream, + PaPulseAudio_StreamPlaybackCb, + stream ); + + /* Just keep on trucking if we are just corked */ + if( pa_stream_get_state( stream->outputStream ) == PA_STREAM_READY + && pa_stream_is_corked( stream->outputStream ) ) + { + PaPulseAudio_Lock( pulseaudioHostApi->mainloop ); + pulseaudioOperation = pa_stream_cork( stream->outputStream, + 0, + PaPulseAudio_CorkSuccessCb, + stream ); + PaPulseAudio_UnLock( pulseaudioHostApi->mainloop ); + + while( pa_operation_get_state( pulseaudioOperation ) == PA_OPERATION_RUNNING) + { + pa_threaded_mainloop_wait( pulseaudioHostApi->mainloop ); + } + + pa_operation_unref( pulseaudioOperation ); + pulseaudioOperation = NULL; + } + else + { + if( stream->outputDevice != paNoDevice ) + { + PA_DEBUG( ("Portaudio %s: %d (%s)\n", + __FUNCTION__, + stream->outputDevice, + pulseaudioHostApi->pulseaudioDeviceNames[stream-> + outputDevice]) ); + } + + PaDeviceIndex defaultOutputDevice; + PaError result = PaUtil_DeviceIndexToHostApiDeviceIndex( &defaultOutputDevice, + pulseaudioHostApi->inheritedHostApiRep.info.defaultOutputDevice, + &(pulseaudioHostApi->inheritedHostApiRep) ); + + /* NULL means default device */ + pulseaudioName = NULL; + + /* If default device is not requested then change to wanted device */ + if( result == paNoError && stream->outputDevice != defaultOutputDevice ) + { + pulseaudioName = pulseaudioHostApi-> + pulseaudioDeviceNames[stream->outputDevice]; + } + + if(result == paNoError) + { + PaPulseAudio_Lock( pulseaudioHostApi->mainloop ); + + if ( ! pa_stream_connect_playback( stream->outputStream, + pulseaudioName, + &stream->outputBufferAttr, + pulseaudioStreamFlags, + NULL, + NULL ) ) + { + pa_stream_set_underflow_callback( stream->outputStream, + PaPulseAudio_StreamUnderflowCb, + stream); + pa_stream_set_started_callback( stream->outputStream, + PaPulseAudio_StreamStartedCb, + stream ); + } + else + { + PA_DEBUG( ("Portaudio %s: Can't write audio!\n", + __FUNCTION__) ); + goto startstreamcb_error; + } + PaPulseAudio_UnLock( pulseaudioHostApi->mainloop ); + + for( waitLoop = 0; waitLoop < 100; waitLoop ++ ) + { + PaPulseAudio_Lock( pulseaudioHostApi->mainloop ); + pulseaudioState = pa_stream_get_state( stream->outputStream ); + PaPulseAudio_UnLock( pulseaudioHostApi->mainloop ); + + if( pulseaudioState = PA_STREAM_READY ) + { + break; + } + else if( pulseaudioState == PA_STREAM_FAILED || + pulseaudioState == PA_STREAM_TERMINATED ) + { + goto startstreamcb_error; + } + + usleep(10000); + } + + } + else + { + goto startstreamcb_error; + } + } + } + + if( !stream->outputStream && + !stream->inputStream ) + { + PA_DEBUG( ("Portaudio %s: Streams not initialized!\n", + __FUNCTION__) ); + goto startstreamcb_error; + } + + /* Make sure we pass no error on intialize */ + ret = paNoError; + + /* Stream is now active */ + stream->isActive = 1; + stream->isStopped = 0; + + /* Allways unlock.. so we don't get locked */ + startstreamcb_end: + return ret; + + error: + startstreamcb_error: + PA_DEBUG( ("Portaudio %s: Can't start audio!\n", + __FUNCTION__) ); + + if( pulseaudioPlaybackStarted || pulseaudioRecordStarted ) + { + PaPulseAudio_AbortStreamCb( stream ); + } + + stream->isActive = 0; + stream->isStopped = 1; + ret = paNotInitialized; + + goto startstreamcb_end; +} + +static PaError RequestStop( PaPulseAudio_Stream * stream, + int abort ) +{ + PaError ret = paNoError; + PaPulseAudio_HostApiRepresentation *pulseaudioHostApi = stream->hostapi; + pa_operation *pulseaudioOperation = NULL; + + PaPulseAudio_Lock( pulseaudioHostApi->mainloop ); + + /* Wait for stream to be stopped */ + stream->isActive = 0; + stream->isStopped = 1; + stream->pulseaudioIsActive = 0; + stream->pulseaudioIsStopped = 1; + + stream->missedBytes = 0; + + /* Test if there is something that we can play */ + if( stream->outputStream + && pa_stream_get_state( stream->outputStream ) == PA_STREAM_READY + && !pa_stream_is_corked( stream->outputStream ) + && !abort ) + { + pulseaudioOperation = pa_stream_cork( stream->outputStream, + 1, + PaPulseAudio_CorkSuccessCb, + stream ); + + while( pa_operation_get_state( pulseaudioOperation ) == PA_OPERATION_RUNNING ) + { + pa_threaded_mainloop_wait( pulseaudioHostApi->mainloop ); + } + + pa_operation_unref( pulseaudioOperation ); + + pulseaudioOperation = NULL; + } + + requeststop_error: + PaPulseAudio_UnLock( pulseaudioHostApi->mainloop ); + stream->isActive = 0; + stream->isStopped = 1; + stream->pulseaudioIsActive = 0; + stream->pulseaudioIsStopped = 1; + + return ret; +} + +PaError PaPulseAudio_StopStreamCb( PaStream * s ) +{ + return RequestStop( (PaPulseAudio_Stream *) s, + 0 ); +} + +PaError PaPulseAudio_AbortStreamCb( PaStream * s ) +{ + return RequestStop( (PaPulseAudio_Stream *) s, + 1 ); +} diff --git a/3rdparty/portaudio/src/hostapi/pulseaudio/pa_linux_pulseaudio_cb_internal.h b/3rdparty/portaudio/src/hostapi/pulseaudio/pa_linux_pulseaudio_cb_internal.h new file mode 100644 index 0000000000000..74e510de956fe --- /dev/null +++ b/3rdparty/portaudio/src/hostapi/pulseaudio/pa_linux_pulseaudio_cb_internal.h @@ -0,0 +1,97 @@ + +/* + * PulseAudio host to play natively in Linux based systems without + * ALSA emulation + * + * Copyright (c) 2014-2023 Tuukka Pasanen + * Copyright (c) 2016 Sqweek + * + * Based on the Open Source API proposed by Ross Bencina + * Copyright (c) 1999-2002 Ross Bencina, Phil Burk + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/* + * The text above constitutes the entire PortAudio license; however, + * the PortAudio community also makes the following non-binding requests: + * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. It is also + * requested that these non-binding requests be included along with the + * license above. + */ + +#ifndef _PA_HOSTAPI_PULSEAUDIO_CB_H_ +#define _PA_HOSTAPI_PULSEAUDIO_CB_H_ + +#include "pa_util.h" +#include "pa_allocation.h" +#include "pa_hostapi.h" +#include "pa_stream.h" +#include "pa_cpuload.h" +#include "pa_process.h" + +#include "pa_unix_util.h" +#include "pa_ringbuffer.h" + + +/* PulseAudio headers */ +#include +#include +#include + +#include "pa_linux_pulseaudio_internal.h" + + +#ifdef __cplusplus +extern "C" +{ +#endif /* __cplusplus */ + +int PaPulseAudio_updateTimeInfo( pa_stream * s, + PaStreamCallbackTimeInfo *timeInfo, + int record ); + +void *PaPulseAudio_processThread( void *userdata ); + +PaError PaPulseAudio_CloseStreamCb( PaStream * stream ); + +PaError PaPulseAudio_StartStreamCb( PaStream * stream ); + +PaError PaPulseAudio_StopStreamCb( PaStream * stream ); + +PaError PaPulseAudio_AbortStreamCb( PaStream * stream ); + +void PaPulseAudio_StreamRecordCb( pa_stream * s, + size_t length, + void *userdata ); + +void PaPulseAudio_StreamPlaybackCb( pa_stream * s, + size_t length, + void *userdata ); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + + +#endif diff --git a/3rdparty/portaudio/src/hostapi/pulseaudio/pa_linux_pulseaudio_internal.h b/3rdparty/portaudio/src/hostapi/pulseaudio/pa_linux_pulseaudio_internal.h new file mode 100644 index 0000000000000..84bff25799310 --- /dev/null +++ b/3rdparty/portaudio/src/hostapi/pulseaudio/pa_linux_pulseaudio_internal.h @@ -0,0 +1,273 @@ + +/* + * PulseAudio host to play natively in Linux based systems without + * ALSA emulation + * + * Copyright (c) 2014-2023 Tuukka Pasanen + * Copyright (c) 2016 Sqweek + * + * Based on the Open Source API proposed by Ross Bencina + * Copyright (c) 1999-2002 Ross Bencina, Phil Burk + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/* + * The text above constitutes the entire PortAudio license; however, + * the PortAudio community also makes the following non-binding requests: + * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. It is also + * requested that these non-binding requests be included along with the + * license above. + */ + +#ifndef _PA_HOSTAPI_PULSEAUDIO_H_ +#define _PA_HOSTAPI_PULSEAUDIO_H_ + +#include "pa_util.h" +#include "pa_allocation.h" +#include "pa_hostapi.h" +#include "pa_stream.h" +#include "pa_cpuload.h" +#include "pa_process.h" + +#include "pa_unix_util.h" +#include "pa_ringbuffer.h" +#include "pa_debugprint.h" + +/* PulseAudio headers */ +#include +#include +#include + +#ifdef __cplusplus +extern "C" +{ +#endif /* __cplusplus */ + +/* prototypes for functions declared in this file */ + +#define PA_PULSEAUDIO_SET_LAST_HOST_ERROR(errorCode, errorText) \ + PaUtil_SetLastHostErrorInfo(paInDevelopment, errorCode, errorText) + +#define PAPULSEAUDIO_MAX_DEVICECOUNT 1024 +#define PAPULSEAUDIO_MAX_DEVICENAME 1024 + +/* Default latency values to expose. Chosen by trial and error to be reasonable. */ +#define PA_PULSEAUDIO_DEFAULT_MIN_LATENCY 0.010 +#define PA_PULSEAUDIO_DEFAULT_MAX_LATENCY 0.080 + +/* Just some value that Pulseaudio can handle */ +#define PAPULSEAUDIO_FRAMESPERBUFFERUNSPEC 32 + +/* Assuming of 2 seconds of 44100 Hz sample rate with FLOAT (4 bytes) and stereo channels (2 channels). + You should have pretty good size buffer with this. If output/intput doesn't happens in 2 second we + have more trouble than this buffer. + @todo change this to something more sophisticated */ +#define PULSEAUDIO_BUFFER_SIZE (96100 * 4 * 2) + +typedef struct +{ + PaUtilHostApiRepresentation inheritedHostApiRep; + PaUtilStreamInterface callbackStreamInterface; + PaUtilStreamInterface blockingStreamInterface; + + PaUtilAllocationGroup *allocations; + + PaHostApiIndex hostApiIndex; + PaDeviceInfo deviceInfoArray[PAPULSEAUDIO_MAX_DEVICECOUNT]; + char *pulseaudioDeviceNames[PAPULSEAUDIO_MAX_DEVICECOUNT]; + pa_sample_spec pulseaudioDefaultSampleSpec; + + /* PulseAudio stuff goes here */ + pa_threaded_mainloop *mainloop; + pa_mainloop_api *mainloopApi; + pa_context *context; + int deviceCount; + pa_time_event *timeEvent; +} +PaPulseAudio_HostApiRepresentation; + +/* PaPulseAudio_Stream - a stream data structure specifically for this implementation */ + +typedef struct PaPulseAudio_Stream +{ + PaUtilStreamRepresentation streamRepresentation; + PaUtilCpuLoadMeasurer cpuLoadMeasurer; + PaUtilBufferProcessor bufferProcessor; + PaPulseAudio_HostApiRepresentation *hostapi; + + unsigned long framesPerHostCallback; + pa_threaded_mainloop *mainloop; + pa_context *context; + pa_sample_spec outputSampleSpec; + pa_sample_spec inputSampleSpec; + pa_stream *outputStream; + pa_stream *inputStream; + pa_buffer_attr outputBufferAttr; + pa_buffer_attr inputBufferAttr; + int outputUnderflows; + int outputChannelCount; + int inputChannelCount; + + size_t maxFramesPerBuffer; + size_t maxFramesHostPerBuffer; + int outputFrameSize; + int inputFrameSize; + + PaDeviceIndex inputDevice; + PaDeviceIndex outputDevice; + + char *outputStreamName; + char *inputStreamName; + + PaUtilRingBuffer inputRing; + + size_t missedBytes; + + /* Used in communication between threads + * + * State machine works like this: + * When stream is wanted to start with Pa_StartStream + * then isActive is 1 if opening of devices goes well + * and isStopped is then 0. + * + * When requested to stop isStopped is 1 on isActive is 0 + * and nothing should be written to ouput or read from input + * anymore + * + * Pulseaudio does not like this as it creates streams and they + * start when they are ready and it can be after we have + * exited Pa_StartStream or before if get's kicked up very fast + * + * pulseaudioIsActive and pulseaudioIsStopped are used to find if + * there is stream active or stopped in pulseaudio side. They + * live their own life besides isActive and isStopped to make sure + * that portaudio will have input and output available before + * reading or writing to stream. + */ + volatile sig_atomic_t isActive; + volatile sig_atomic_t isStopped; + volatile sig_atomic_t pulseaudioIsActive; + volatile sig_atomic_t pulseaudioIsStopped; + +} +PaPulseAudio_Stream; + +/* PulseAudio Error checking macro */ +#define PA_PULSEAUDIO_IS_ERROR(pastream, errorCode) \ + if( !(pastream) || \ + !(pastream)->context || \ + !PA_CONTEXT_IS_GOOD( pa_context_get_state( (pastream)->context ) ) || \ + ( (pastream)->outputStream && \ + !PA_STREAM_IS_GOOD( pa_stream_get_state( (pastream)->outputStream ) ) ) || \ + ( (pastream)->inputStream && \ + !PA_STREAM_IS_GOOD( pa_stream_get_state( (pastream)->inputStream ) ) ) ) \ + { \ + if( !(pastream) || \ + ( (pastream)->context && \ + pa_context_get_state( (pastream)->context ) == PA_CONTEXT_FAILED ) || \ + ( (pastream)->outputStream && \ + pa_stream_get_state( (pastream)->outputStream ) == PA_STREAM_FAILED ) || \ + ( (pastream)->inputStream && \ + pa_stream_get_state( (pastream)->inputStream ) == PA_STREAM_FAILED ) ) \ + { \ + return errorCode; \ + } \ + } \ + if( !pastream->isActive || pastream->isStopped ) \ + { \ + return paStreamIsStopped; \ + } + +void PaPulseAudio_Lock( pa_threaded_mainloop *mainloop ); + +void PaPulseAudio_UnLock( pa_threaded_mainloop *mainloop ); + +PaError PaPulseAudio_Initialize( PaUtilHostApiRepresentation ** hostApi, + PaHostApiIndex index ); + +void Terminate( struct PaUtilHostApiRepresentation *hostApi ); + + +PaError IsFormatSupported( struct PaUtilHostApiRepresentation *hostApi, + const PaStreamParameters * inputParameters, + const PaStreamParameters * outputParameters, + double sampleRate ); + +PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi, + PaStream ** s, + const PaStreamParameters * inputParameters, + const PaStreamParameters * outputParameters, + double sampleRate, + unsigned long framesPerBuffer, + PaStreamFlags streamFlags, + PaStreamCallback * streamCallback, + void *userData ); + + +PaError IsStreamStopped( PaStream * s ); +PaError IsStreamActive( PaStream * stream ); + +PaTime GetStreamTime( PaStream * stream ); +double GetStreamCpuLoad( PaStream * stream ); + +PaPulseAudio_HostApiRepresentation *PaPulseAudio_New( void ); +void PaPulseAudio_Free( PaPulseAudio_HostApiRepresentation * ptr ); + +int PaPulseAudio_CheckConnection( PaPulseAudio_HostApiRepresentation * ptr ); + +void PaPulseAudio_CheckContextStateCb( pa_context * c, + void *userdata ); +void PaPulseAudio_ServerInfoCb( pa_context *c, + const pa_server_info *i, + void *userdata ); + +void PaPulseAudio_SinkListCb( pa_context * c, + const pa_sink_info * l, + int eol, + void *userdata ); + +void PaPulseAudio_SourceListCb( pa_context * c, + const pa_source_info * l, + int eol, + void *userdata ); + +void PaPulseAudio_StreamStateCb( pa_stream * s, + void *userdata ); + +void PaPulseAudio_StreamStartedCb( pa_stream * s, + void *userdata ); + +void PaPulseAudio_StreamUnderflowCb( pa_stream * s, + void *userdata ); + +PaError PaPulseAudio_ConvertPortaudioFormatToPaPulseAudio_( PaSampleFormat portaudiosf, + pa_sample_spec * pulseaudiosf +); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + + +#endif diff --git a/3rdparty/portaudio/src/hostapi/wasapi/pa_win_wasapi.c b/3rdparty/portaudio/src/hostapi/wasapi/pa_win_wasapi.c index c11393954e230..dcbef04db2015 100644 --- a/3rdparty/portaudio/src/hostapi/wasapi/pa_win_wasapi.c +++ b/3rdparty/portaudio/src/hostapi/wasapi/pa_win_wasapi.c @@ -44,6 +44,7 @@ */ #include +#include #include #include #include @@ -106,10 +107,11 @@ #include "pa_stream.h" #include "pa_cpuload.h" #include "pa_process.h" -#include "pa_win_wasapi.h" #include "pa_debugprint.h" #include "pa_ringbuffer.h" +#include "pa_win_version.h" #include "pa_win_coinitialize.h" +#include "pa_win_wasapi.h" #if !defined(NTDDI_VERSION) || (defined(__GNUC__) && (__GNUC__ <= 6) && !defined(__MINGW64__)) @@ -281,9 +283,26 @@ PA_DEFINE_IID(IPart, AE2DE0E4, 5BCA, 4F2D, aa, 46, 5d, 13, f8, fd PA_DEFINE_IID(IKsJackDescription, 4509F757, 2D46, 4637, 8e, 62, ce, 7d, b9, 44, f5, 7b); // Media formats: -__DEFINE_GUID(pa_KSDATAFORMAT_SUBTYPE_PCM, 0x00000001, 0x0000, 0x0010, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 ); -__DEFINE_GUID(pa_KSDATAFORMAT_SUBTYPE_ADPCM, 0x00000002, 0x0000, 0x0010, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 ); -__DEFINE_GUID(pa_KSDATAFORMAT_SUBTYPE_IEEE_FLOAT, 0x00000003, 0x0000, 0x0010, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 ); +__DEFINE_GUID(pa_KSDATAFORMAT_SUBTYPE_PCM, 0x00000001, 0x0000, 0x0010, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 ); +__DEFINE_GUID(pa_KSDATAFORMAT_SUBTYPE_ADPCM, 0x00000002, 0x0000, 0x0010, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 ); +__DEFINE_GUID(pa_KSDATAFORMAT_SUBTYPE_IEEE_FLOAT, 0x00000003, 0x0000, 0x0010, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 ); +__DEFINE_GUID(pa_KSDATAFORMAT_SUBTYPE_IEC61937_PCM, 0x00000000, 0x0000, 0x0010, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 ); + +#ifndef _WAVEFORMATEXTENSIBLE_IEC61937_ + #define _WAVEFORMATEXTENSIBLE_IEC61937_ +typedef struct { + WAVEFORMATEXTENSIBLE FormatExt; + DWORD dwEncodedSamplesPerSec; + DWORD dwEncodedChannelCount; + DWORD dwAverageBytesPerSec; +} WAVEFORMATEXTENSIBLE_IEC61937, *PWAVEFORMATEXTENSIBLE_IEC61937; +#endif // !_WAVEFORMATEXTENSIBLE_IEC61937_ + +typedef union _WAVEFORMATEXTENSIBLE_UNION +{ + WAVEFORMATEXTENSIBLE ext; + WAVEFORMATEXTENSIBLE_IEC61937 iec61937; +} WAVEFORMATEXTENSIBLE_UNION; #ifdef __IAudioClient2_INTERFACE_DEFINED__ typedef enum _pa_AUDCLNT_STREAMOPTIONS { @@ -534,7 +553,7 @@ typedef struct PaWasapiSubStream #endif IAudioClient *clientProc; - WAVEFORMATEXTENSIBLE wavex; + WAVEFORMATEXTENSIBLE_UNION wavexu; UINT32 bufferSize; REFERENCE_TIME deviceLatency; REFERENCE_TIME period; @@ -1164,193 +1183,24 @@ static BOOL IsWow64() #endif } - -// ------------------------------------------------------------------------------------------ -typedef enum EWindowsVersion -{ - WINDOWS_UNKNOWN = 0, - WINDOWS_VISTA_SERVER2008, - WINDOWS_7_SERVER2008R2, - WINDOWS_8_SERVER2012, - WINDOWS_8_1_SERVER2012R2, - WINDOWS_10_SERVER2016, - WINDOWS_FUTURE -} -EWindowsVersion; -// Alternative way for checking Windows version (allows to check version on Windows 8.1 and up) -#ifndef PA_WINRT -static BOOL IsWindowsVersionOrGreater(WORD wMajorVersion, WORD wMinorVersion, WORD wServicePackMajor) -{ - typedef ULONGLONG (NTAPI *LPFN_VERSETCONDITIONMASK)(ULONGLONG ConditionMask, DWORD TypeMask, BYTE Condition); - typedef BOOL (WINAPI *LPFN_VERIFYVERSIONINFO)(LPOSVERSIONINFOEXA lpVersionInformation, DWORD dwTypeMask, DWORDLONG dwlConditionMask); - - LPFN_VERSETCONDITIONMASK fnVerSetConditionMask; - LPFN_VERIFYVERSIONINFO fnVerifyVersionInfo; - OSVERSIONINFOEXA osvi = { sizeof(osvi), 0, 0, 0, 0, {0}, 0, 0 }; - DWORDLONG dwlConditionMask; - - fnVerSetConditionMask = (LPFN_VERSETCONDITIONMASK)GetProcAddress(GetModuleHandleA("kernel32"), "VerSetConditionMask"); - fnVerifyVersionInfo = (LPFN_VERIFYVERSIONINFO)GetProcAddress(GetModuleHandleA("kernel32"), "VerifyVersionInfoA"); - - if ((fnVerSetConditionMask == NULL) || (fnVerifyVersionInfo == NULL)) - return FALSE; - - dwlConditionMask = fnVerSetConditionMask( - fnVerSetConditionMask( - fnVerSetConditionMask( - 0, VER_MAJORVERSION, VER_GREATER_EQUAL), - VER_MINORVERSION, VER_GREATER_EQUAL), - VER_SERVICEPACKMAJOR, VER_GREATER_EQUAL); - - osvi.dwMajorVersion = wMajorVersion; - osvi.dwMinorVersion = wMinorVersion; - osvi.wServicePackMajor = wServicePackMajor; - - return (fnVerifyVersionInfo(&osvi, VER_MAJORVERSION | VER_MINORVERSION | VER_SERVICEPACKMAJOR, dwlConditionMask) != FALSE); -} -#endif -// Get Windows version -// note: We are trying to get Windows version starting from Windows Vista. Earlier OS versions -// will fall into WINDOWS_UNKNOWN case. -static EWindowsVersion GetWindowsVersion() -{ -#ifndef PA_WINRT - static EWindowsVersion version = WINDOWS_UNKNOWN; - - if (version == WINDOWS_UNKNOWN) - { - DWORD dwMajorVersion = 0xFFFFFFFFU, dwMinorVersion = 0, dwBuild = 0; - - // RTL_OSVERSIONINFOW equals OSVERSIONINFOW but it is missing inb MinGW winnt.h header, - // thus use OSVERSIONINFOW for greater portability - typedef NTSTATUS (WINAPI *LPFN_RTLGETVERSION)(POSVERSIONINFOW lpVersionInformation); - LPFN_RTLGETVERSION fnRtlGetVersion; - - #define NTSTATUS_SUCCESS ((NTSTATUS)0x00000000L) - - // RtlGetVersion must be able to provide true Windows version (Windows 10 may be reported as Windows 8 - // by GetVersion API) - if ((fnRtlGetVersion = (LPFN_RTLGETVERSION)GetProcAddress(GetModuleHandleA("ntdll"), "RtlGetVersion")) != NULL) - { - OSVERSIONINFOW ver = { sizeof(OSVERSIONINFOW), 0, 0, 0, 0, {0} }; - - if (fnRtlGetVersion(&ver) == NTSTATUS_SUCCESS) - { - dwMajorVersion = ver.dwMajorVersion; - dwMinorVersion = ver.dwMinorVersion; - dwBuild = ver.dwBuildNumber; - } - - PRINT(("WASAPI: getting Windows version with RtlGetVersion(): major=%d, minor=%d, build=%d\n", dwMajorVersion, dwMinorVersion, dwBuild)); - } - - #undef NTSTATUS_SUCCESS - - // fallback to GetVersion if RtlGetVersion is missing - if (dwMajorVersion == 0xFFFFFFFFU) - { - typedef DWORD (WINAPI *LPFN_GETVERSION)(VOID); - LPFN_GETVERSION fnGetVersion; - - if ((fnGetVersion = (LPFN_GETVERSION)GetProcAddress(GetModuleHandleA("kernel32"), "GetVersion")) != NULL) - { - DWORD dwVersion = fnGetVersion(); - - dwMajorVersion = (DWORD)(LOBYTE(LOWORD(dwVersion))); - dwMinorVersion = (DWORD)(HIBYTE(LOWORD(dwVersion))); - - if (dwVersion < 0x80000000) - dwBuild = (DWORD)(HIWORD(dwVersion)); - - PRINT(("WASAPI: getting Windows version with GetVersion(): major=%d, minor=%d, build=%d\n", dwMajorVersion, dwMinorVersion, dwBuild)); - } - } - - if (dwMajorVersion != 0xFFFFFFFFU) - { - switch (dwMajorVersion) - { - case 0: - case 1: - case 2: - case 3: - case 4: - case 5: - break; // skip lower - case 6: - switch (dwMinorVersion) - { - case 0: version = WINDOWS_VISTA_SERVER2008; break; - case 1: version = WINDOWS_7_SERVER2008R2; break; - case 2: version = WINDOWS_8_SERVER2012; break; - case 3: version = WINDOWS_8_1_SERVER2012R2; break; - default: version = WINDOWS_FUTURE; break; - } - break; - case 10: - switch (dwMinorVersion) - { - case 0: version = WINDOWS_10_SERVER2016; break; - default: version = WINDOWS_FUTURE; break; - } - break; - default: - version = WINDOWS_FUTURE; - break; - } - } - // fallback to VerifyVersionInfo if RtlGetVersion and GetVersion are missing - else - { - PRINT(("WASAPI: getting Windows version with VerifyVersionInfo()\n")); - - if (IsWindowsVersionOrGreater(10, 0, 0)) - version = WINDOWS_10_SERVER2016; - else - if (IsWindowsVersionOrGreater(6, 3, 0)) - version = WINDOWS_8_1_SERVER2012R2; - else - if (IsWindowsVersionOrGreater(6, 2, 0)) - version = WINDOWS_8_SERVER2012; - else - if (IsWindowsVersionOrGreater(6, 1, 0)) - version = WINDOWS_7_SERVER2008R2; - else - if (IsWindowsVersionOrGreater(6, 0, 0)) - version = WINDOWS_VISTA_SERVER2008; - else - version = WINDOWS_FUTURE; - } - - PRINT(("WASAPI: Windows version = %d\n", version)); - } - - return version; -#else - #if (_WIN32_WINNT >= _WIN32_WINNT_WIN10) - return WINDOWS_10_SERVER2016; - #else - return WINDOWS_8_SERVER2012; - #endif -#endif -} - // ------------------------------------------------------------------------------------------ static BOOL UseWOW64Workaround() { // note: WOW64 bug is common to Windows Vista x64, thus we fall back to safe Poll-driven // method. Windows 7 x64 seems has WOW64 bug fixed. - return (IsWow64() && (GetWindowsVersion() == WINDOWS_VISTA_SERVER2008)); + return (IsWow64() && (PaWinUtil_GetOsVersion() == paOsVersionWindowsVistaServer2008)); } // ------------------------------------------------------------------------------------------ static UINT32 GetAudioClientVersion() { - if (GetWindowsVersion() >= WINDOWS_10_SERVER2016) + PaOsVersion version = PaWinUtil_GetOsVersion(); + + if (version >= paOsVersionWindows10Server2016) return 3; else - if (GetWindowsVersion() >= WINDOWS_8_SERVER2012) + if (version >= paOsVersionWindows8Server2012) return 2; return 1; @@ -1764,11 +1614,11 @@ static HRESULT ActivateAudioInterface(const PaWasapiDeviceInfo *deviceInfo, cons switch (streamInfo->streamOption) { case eStreamOptionRaw: - if (GetWindowsVersion() >= WINDOWS_8_1_SERVER2012R2) + if (PaWinUtil_GetOsVersion() >= paOsVersionWindows8_1Server2012R2) audioProps.Options = pa_AUDCLNT_STREAMOPTIONS_RAW; break; case eStreamOptionMatchFormat: - if (GetWindowsVersion() >= WINDOWS_10_SERVER2016) + if (PaWinUtil_GetOsVersion() >= paOsVersionWindows10Server2016) audioProps.Options = pa_AUDCLNT_STREAMOPTIONS_MATCH_FORMAT; break; } @@ -2440,7 +2290,7 @@ PaError PaWasapi_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiInd #ifndef PA_WINRT // Fail safely for any Windows version below Windows Vista - if (GetWindowsVersion() == WINDOWS_UNKNOWN) + if (PaWinUtil_GetOsVersion() < paOsVersionWindowsVistaServer2008) { PRINT(("WASAPI: Unsupported Windows version!\n")); return paNoError; @@ -2652,7 +2502,7 @@ int PaWasapi_GetDeviceCurrentFormat( PaStream *pStream, void *pFormat, unsigned if (stream == NULL) return paBadStreamPtr; - format = (bOutput == TRUE ? &stream->out.wavex : &stream->in.wavex); + format = (bOutput == TRUE ? &stream->out.wavexu.ext : &stream->in.wavexu.ext); size = min(formatSize, (UINT32)sizeof(*format)); memcpy(pFormat, format, size); @@ -2856,9 +2706,7 @@ static void LogWAVEFORMATEXTENSIBLE(const WAVEFORMATEXTENSIBLE *in) // ------------------------------------------------------------------------------------------ PaSampleFormat WaveToPaFormat(const WAVEFORMATEXTENSIBLE *fmtext) { - const WAVEFORMATEX *fmt = (WAVEFORMATEX *)fmtext; - - switch (fmt->wFormatTag) + switch (fmtext->Format.wFormatTag) { case WAVE_FORMAT_EXTENSIBLE: { if (IsEqualGUID(&fmtext->SubFormat, &pa_KSDATAFORMAT_SUBTYPE_IEEE_FLOAT)) @@ -2869,7 +2717,7 @@ PaSampleFormat WaveToPaFormat(const WAVEFORMATEXTENSIBLE *fmtext) else if (IsEqualGUID(&fmtext->SubFormat, &pa_KSDATAFORMAT_SUBTYPE_PCM)) { - switch (fmt->wBitsPerSample) + switch (fmtext->Format.wBitsPerSample) { case 32: return paInt32; case 24: return paInt24; @@ -2877,13 +2725,25 @@ PaSampleFormat WaveToPaFormat(const WAVEFORMATEXTENSIBLE *fmtext) case 8: return paUInt8; } } + else + // KSDATAFORMAT_SUBTYPE_IEC61937_PCM is a naked format GUID which has different Data1 and Data2 + // depending on the passthrough format, for the possible values refer Microsoft documentation + // "Representing Formats for IEC 61937 Transmissions". Here we check if Data3 and Data4 match + // assuming that if they do match then we have KSDATAFORMAT_SUBTYPE_IEC61937_XXX format. + // Currently PA WASAPI is using KSDATAFORMAT_SUBTYPE_IEEE_FLOAT and KSDATAFORMAT_SUBTYPE_PCM + // therefore this check is reliable in this way. + if (!memcmp(&fmtext->SubFormat.Data3, &pa_KSDATAFORMAT_SUBTYPE_IEC61937_PCM.Data3, + (sizeof(GUID) - offsetof(GUID, Data3)))) + { + return paInt16; + } break; } case WAVE_FORMAT_IEEE_FLOAT: return paFloat32; case WAVE_FORMAT_PCM: { - switch (fmt->wBitsPerSample) + switch (fmtext->Format.wBitsPerSample) { case 32: return paInt32; case 24: return paInt24; @@ -2897,7 +2757,7 @@ PaSampleFormat WaveToPaFormat(const WAVEFORMATEXTENSIBLE *fmtext) } // ------------------------------------------------------------------------------------------ -static PaError MakeWaveFormatFromParams(WAVEFORMATEXTENSIBLE *wavex, const PaStreamParameters *params, +static PaError MakeWaveFormatFromParams(WAVEFORMATEXTENSIBLE_UNION *wavexu, const PaStreamParameters *params, double sampleRate, BOOL packedOnly) { WORD bitsPerSample; @@ -2905,6 +2765,8 @@ static PaError MakeWaveFormatFromParams(WAVEFORMATEXTENSIBLE *wavex, const PaStr DWORD channelMask = 0; BOOL useExtensible = (params->channelCount > 2); // format is always forced for >2 channels format PaWasapiStreamInfo *streamInfo = (PaWasapiStreamInfo *)params->hostApiSpecificStreamInfo; + WAVEFORMATEXTENSIBLE *wavex = (WAVEFORMATEXTENSIBLE *)wavexu; + BOOL passthroughMode = ((streamInfo != NULL) && (streamInfo->flags & paWinWasapiPassthrough)); // Convert PaSampleFormat to valid data bits if ((bitsPerSample = PaSampleFormatToBitsPerSample(params->sampleFormat)) == 0) @@ -2917,9 +2779,9 @@ static PaError MakeWaveFormatFromParams(WAVEFORMATEXTENSIBLE *wavex, const PaStr useExtensible = TRUE; } - memset(wavex, 0, sizeof(*wavex)); + memset(wavexu, 0, sizeof(*wavexu)); - old = (WAVEFORMATEX *)wavex; + old = (WAVEFORMATEX *)wavex; old->nChannels = (WORD)params->channelCount; old->nSamplesPerSec = (DWORD)sampleRate; old->wBitsPerSample = bitsPerSample; @@ -2937,18 +2799,35 @@ static PaError MakeWaveFormatFromParams(WAVEFORMATEXTENSIBLE *wavex, const PaStr // WAVEFORMATEX if (!useExtensible) { - old->wFormatTag = WAVE_FORMAT_PCM; + old->wFormatTag = WAVE_FORMAT_PCM; } // WAVEFORMATEXTENSIBLE else { old->wFormatTag = WAVE_FORMAT_EXTENSIBLE; - old->cbSize = sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX); + old->cbSize = (passthroughMode ? (sizeof(WAVEFORMATEXTENSIBLE_IEC61937) - sizeof(WAVEFORMATEX)) + : (sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX))); + + if (passthroughMode) + { + WAVEFORMATEXTENSIBLE_IEC61937 *wavex_61937 = (WAVEFORMATEXTENSIBLE_IEC61937 *)wavexu; + + wavex->SubFormat = pa_KSDATAFORMAT_SUBTYPE_IEC61937_PCM; + wavex->SubFormat.Data1 = (streamInfo->passthrough.formatId >> 16); + wavex->SubFormat.Data2 = (USHORT)(streamInfo->passthrough.formatId & 0x0000FFFF); - if ((params->sampleFormat & ~paNonInterleaved) == paFloat32) + wavex_61937->dwEncodedSamplesPerSec = streamInfo->passthrough.encodedSamplesPerSec; + wavex_61937->dwEncodedChannelCount = streamInfo->passthrough.encodedChannelCount; + wavex_61937->dwAverageBytesPerSec = streamInfo->passthrough.averageBytesPerSec; + } + else if ((params->sampleFormat & ~paNonInterleaved) == paFloat32) + { wavex->SubFormat = pa_KSDATAFORMAT_SUBTYPE_IEEE_FLOAT; + } else + { wavex->SubFormat = pa_KSDATAFORMAT_SUBTYPE_PCM; + } wavex->Samples.wValidBitsPerSample = bitsPerSample; @@ -2995,11 +2874,11 @@ static PaError MakeWaveFormatFromParams(WAVEFORMATEXTENSIBLE *wavex, const PaStr // ------------------------------------------------------------------------------------------ static HRESULT GetAlternativeSampleFormatExclusive(IAudioClient *client, double sampleRate, - const PaStreamParameters *params, WAVEFORMATEXTENSIBLE *outWavex, BOOL packedSampleFormatOnly) + const PaStreamParameters *params, WAVEFORMATEXTENSIBLE_UNION *outWavexU, BOOL packedSampleFormatOnly) { HRESULT hr = !S_OK; AUDCLNT_SHAREMODE shareMode = AUDCLNT_SHAREMODE_EXCLUSIVE; - WAVEFORMATEXTENSIBLE testFormat; + WAVEFORMATEXTENSIBLE_UNION testFormat; PaStreamParameters testParams; int i; static const PaSampleFormat bestToWorst[] = { paInt32, paInt24, paFloat32, paInt16 }; @@ -3012,9 +2891,9 @@ static HRESULT GetAlternativeSampleFormatExclusive(IAudioClient *client, double if (MakeWaveFormatFromParams(&testFormat, &testParams, sampleRate, packedSampleFormatOnly) == paNoError) { - if ((hr = IAudioClient_IsFormatSupported(client, shareMode, &testFormat.Format, NULL)) == S_OK) + if ((hr = IAudioClient_IsFormatSupported(client, shareMode, &testFormat.ext.Format, NULL)) == S_OK) { - (*outWavex) = testFormat; + (*outWavexU) = testFormat; return hr; } } @@ -3026,9 +2905,9 @@ static HRESULT GetAlternativeSampleFormatExclusive(IAudioClient *client, double if (MakeWaveFormatFromParams(&testFormat, &testParams, sampleRate, packedSampleFormatOnly) == paNoError) { - if ((hr = IAudioClient_IsFormatSupported(client, shareMode, &testFormat.Format, NULL)) == S_OK) + if ((hr = IAudioClient_IsFormatSupported(client, shareMode, &testFormat.ext.Format, NULL)) == S_OK) { - (*outWavex) = testFormat; + (*outWavexU) = testFormat; return hr; } } @@ -3043,9 +2922,9 @@ static HRESULT GetAlternativeSampleFormatExclusive(IAudioClient *client, double if (MakeWaveFormatFromParams(&testFormat, &testParams, sampleRate, packedSampleFormatOnly) == paNoError) { - if ((hr = IAudioClient_IsFormatSupported(client, shareMode, &testFormat.Format, NULL)) == S_OK) + if ((hr = IAudioClient_IsFormatSupported(client, shareMode, &testFormat.ext.Format, NULL)) == S_OK) { - (*outWavex) = testFormat; + (*outWavexU) = testFormat; return hr; } } @@ -3056,13 +2935,14 @@ static HRESULT GetAlternativeSampleFormatExclusive(IAudioClient *client, double // ------------------------------------------------------------------------------------------ static PaError GetClosestFormat(IAudioClient *client, double sampleRate, const PaStreamParameters *_params, - AUDCLNT_SHAREMODE shareMode, WAVEFORMATEXTENSIBLE *outWavex, BOOL output) + AUDCLNT_SHAREMODE shareMode, WAVEFORMATEXTENSIBLE_UNION *outWavexU, BOOL output) { PaWasapiStreamInfo *streamInfo = (PaWasapiStreamInfo *)_params->hostApiSpecificStreamInfo; WAVEFORMATEX *sharedClosestMatch = NULL; HRESULT hr = !S_OK; PaStreamParameters params = (*_params); const BOOL explicitFormat = (streamInfo != NULL) && ((streamInfo->flags & paWinWasapiExplicitSampleFormat) == paWinWasapiExplicitSampleFormat); + WAVEFORMATEXTENSIBLE *outWavex = (WAVEFORMATEXTENSIBLE *)outWavexU; (void)output; /* It was not noticed that 24-bit Input producing no output while device accepts this format. @@ -3074,10 +2954,10 @@ static PaError GetClosestFormat(IAudioClient *client, double sampleRate, const P params.sampleFormat = paFloat32;*/ // <<< The silence was due to missing Int32_To_Int24_Dither implementation // Try standard approach, e.g. if data is > 16 bits it will be packed into 32-bit containers - MakeWaveFormatFromParams(outWavex, ¶ms, sampleRate, FALSE); + MakeWaveFormatFromParams(outWavexU, ¶ms, sampleRate, FALSE); // If built-in PCM converter requested then shared mode format will always succeed - if ((GetWindowsVersion() >= WINDOWS_7_SERVER2008R2) && + if ((PaWinUtil_GetOsVersion() >= paOsVersionWindows7Server2008R2) && (shareMode == AUDCLNT_SHAREMODE_SHARED) && ((streamInfo != NULL) && (streamInfo->flags & paWinWasapiAutoConvert))) return paFormatIsSupported; @@ -3088,7 +2968,7 @@ static PaError GetClosestFormat(IAudioClient *client, double sampleRate, const P if ((hr != S_OK) && (shareMode == AUDCLNT_SHAREMODE_EXCLUSIVE)) { // Enforce packed only format, e.g. data bits will not be packed into 32-bit containers in any case - MakeWaveFormatFromParams(outWavex, ¶ms, sampleRate, TRUE); + MakeWaveFormatFromParams(outWavexU, ¶ms, sampleRate, TRUE); hr = IAudioClient_IsFormatSupported(client, shareMode, &outWavex->Format, NULL); } @@ -3136,11 +3016,11 @@ static PaError GetClosestFormat(IAudioClient *client, double sampleRate, const P if ((shareMode == AUDCLNT_SHAREMODE_EXCLUSIVE) && !explicitFormat) { // Try standard approach, e.g. if data is > 16 bits it will be packed into 32-bit containers - if ((hr = GetAlternativeSampleFormatExclusive(client, sampleRate, ¶ms, outWavex, FALSE)) == S_OK) + if ((hr = GetAlternativeSampleFormatExclusive(client, sampleRate, ¶ms, outWavexU, FALSE)) == S_OK) return paFormatIsSupported; // Enforce packed only format, e.g. data bits will not be packed into 32-bit containers in any case - if ((hr = GetAlternativeSampleFormatExclusive(client, sampleRate, ¶ms, outWavex, TRUE)) == S_OK) + if ((hr = GetAlternativeSampleFormatExclusive(client, sampleRate, ¶ms, outWavexU, TRUE)) == S_OK) return paFormatIsSupported; // Log failure @@ -3250,7 +3130,7 @@ static PaError IsFormatSupported( struct PaUtilHostApiRepresentation *hostApi, if (inputParameters != NULL) { - WAVEFORMATEXTENSIBLE wavex; + WAVEFORMATEXTENSIBLE_UNION wavex; HRESULT hr; PaError answer; AUDCLNT_SHAREMODE shareMode = AUDCLNT_SHAREMODE_SHARED; @@ -3276,7 +3156,7 @@ static PaError IsFormatSupported( struct PaUtilHostApiRepresentation *hostApi, if (outputParameters != NULL) { HRESULT hr; - WAVEFORMATEXTENSIBLE wavex; + WAVEFORMATEXTENSIBLE_UNION wavex; PaError answer; AUDCLNT_SHAREMODE shareMode = AUDCLNT_SHAREMODE_SHARED; outputStreamInfo = (PaWasapiStreamInfo *)outputParameters->hostApiSpecificStreamInfo; @@ -3353,11 +3233,11 @@ static void _CalculateAlignedPeriod(PaWasapiSubStream *pSub, UINT32 *nFramesPerL if (pSub->shareMode == AUDCLNT_SHAREMODE_EXCLUSIVE) { (*nFramesPerLatency) = AlignFramesPerBuffer((*nFramesPerLatency), - pSub->wavex.Format.nBlockAlign, pAlignFunc); + pSub->wavexu.ext.Format.nBlockAlign, pAlignFunc); } // Calculate period - pSub->period = MakeHnsPeriod((*nFramesPerLatency), pSub->wavex.Format.nSamplesPerSec); + pSub->period = MakeHnsPeriod((*nFramesPerLatency), pSub->wavexu.ext.Format.nSamplesPerSec); } // ------------------------------------------------------------------------------------------ @@ -3388,9 +3268,9 @@ static void _CalculatePeriodicity(PaWasapiSubStream *pSub, BOOL output, REFERENC // Align frames backwards, so device will likely make buffer read ready when we are ready // to read it (our scheduling will wait for amount of millisoconds of frames_per_buffer) alignedFrames = AlignFramesPerBuffer(pSub->params.frames_per_buffer, - pSub->wavex.Format.nBlockAlign, ALIGN_BWD); + pSub->wavexu.ext.Format.nBlockAlign, ALIGN_BWD); - userPeriodicity = MakeHnsPeriod(alignedFrames, pSub->wavex.Format.nSamplesPerSec); + userPeriodicity = MakeHnsPeriod(alignedFrames, pSub->wavexu.ext.Format.nSamplesPerSec); // Must not be larger than buffer size if (userPeriodicity > pSub->period) @@ -3445,7 +3325,7 @@ static HRESULT CreateAudioClient(PaWasapiStream *pStream, PaWasapiSubStream *pSu } // Get closest format - if ((error = GetClosestFormat(audioClient, sampleRate, params, pSub->shareMode, &pSub->wavex, output)) != paFormatIsSupported) + if ((error = GetClosestFormat(audioClient, sampleRate, params, pSub->shareMode, &pSub->wavexu, output)) != paFormatIsSupported) { (*pa_error) = error; LogHostError(hr = AUDCLNT_E_UNSUPPORTED_FORMAT); @@ -3453,10 +3333,10 @@ static HRESULT CreateAudioClient(PaWasapiStream *pStream, PaWasapiSubStream *pSu } // Check for Mono <<>> Stereo workaround - if ((params->channelCount == 1) && (pSub->wavex.Format.nChannels == 2)) + if ((params->channelCount == 1) && (pSub->wavexu.ext.Format.nChannels == 2)) { // select mixer - pSub->monoMixer = GetMonoToStereoMixer(&pSub->wavex, (output ? MIX_DIR__1TO2 : MIX_DIR__2TO1_L)); + pSub->monoMixer = GetMonoToStereoMixer(&pSub->wavexu.ext, (output ? MIX_DIR__1TO2 : MIX_DIR__2TO1_L)); if (pSub->monoMixer == NULL) { (*pa_error) = paInvalidChannelCount; @@ -3470,7 +3350,7 @@ static HRESULT CreateAudioClient(PaWasapiStream *pStream, PaWasapiSubStream *pSu (!pSub->streamFlags || ((pSub->streamFlags & AUDCLNT_STREAMFLAGS_EVENTCALLBACK) == 0))) { framesPerLatency = _CalculateFramesPerHostBuffer(userFramesPerBuffer, params->suggestedLatency, - pSub->wavex.Format.nSamplesPerSec); + pSub->wavexu.ext.Format.nSamplesPerSec); } else { @@ -3479,16 +3359,16 @@ static HRESULT CreateAudioClient(PaWasapiStream *pStream, PaWasapiSubStream *pSu #endif // Work 1:1 with user buffer (only polling allows to use >1) - framesPerLatency += MakeFramesFromHns(SecondsTonano100(params->suggestedLatency), pSub->wavex.Format.nSamplesPerSec); + framesPerLatency += MakeFramesFromHns(SecondsTonano100(params->suggestedLatency), pSub->wavexu.ext.Format.nSamplesPerSec); // Force Polling if overall latency is >= 21.33ms as it allows to use 100% CPU in a callback, // or user specified latency parameter. #ifdef PA_WASAPI_FORCE_POLL_IF_LARGE_BUFFER - overall = MakeHnsPeriod(framesPerLatency, pSub->wavex.Format.nSamplesPerSec); + overall = MakeHnsPeriod(framesPerLatency, pSub->wavexu.ext.Format.nSamplesPerSec); if (overall >= (106667 * 2)/*21.33ms*/) { framesPerLatency = _CalculateFramesPerHostBuffer(userFramesPerBuffer, params->suggestedLatency, - pSub->wavex.Format.nSamplesPerSec); + pSub->wavexu.Format.nSamplesPerSec); // Use Polling interface pSub->streamFlags &= ~AUDCLNT_STREAMFLAGS_EVENTCALLBACK; @@ -3503,7 +3383,7 @@ static HRESULT CreateAudioClient(PaWasapiStream *pStream, PaWasapiSubStream *pSu // Avoid 0 frames if (framesPerLatency == 0) - framesPerLatency = MakeFramesFromHns(pInfo->DefaultDevicePeriod, pSub->wavex.Format.nSamplesPerSec); + framesPerLatency = MakeFramesFromHns(pInfo->DefaultDevicePeriod, pSub->wavexu.ext.Format.nSamplesPerSec); // Exclusive Input stream renders data in 6 packets, we must set then the size of // single packet, total buffer size, e.g. required latency will be PacketSize * 6 @@ -3527,7 +3407,7 @@ static HRESULT CreateAudioClient(PaWasapiStream *pStream, PaWasapiSubStream *pSu pSub->period = pInfo->DefaultDevicePeriod; // Recalculate aligned period - framesPerLatency = MakeFramesFromHns(pSub->period, pSub->wavex.Format.nSamplesPerSec); + framesPerLatency = MakeFramesFromHns(pSub->period, pSub->wavexu.ext.Format.nSamplesPerSec); _CalculateAlignedPeriod(pSub, &framesPerLatency, ALIGN_BWD); } } @@ -3538,7 +3418,7 @@ static HRESULT CreateAudioClient(PaWasapiStream *pStream, PaWasapiSubStream *pSu pSub->period = pInfo->MinimumDevicePeriod; // Recalculate aligned period - framesPerLatency = MakeFramesFromHns(pSub->period, pSub->wavex.Format.nSamplesPerSec); + framesPerLatency = MakeFramesFromHns(pSub->period, pSub->wavexu.ext.Format.nSamplesPerSec); _CalculateAlignedPeriod(pSub, &framesPerLatency, ALIGN_FWD); } } @@ -3566,7 +3446,7 @@ static HRESULT CreateAudioClient(PaWasapiStream *pStream, PaWasapiSubStream *pSu pSub->period = MAX_BUFFER_EVENT_DURATION; // Recalculate aligned period - framesPerLatency = MakeFramesFromHns(pSub->period, pSub->wavex.Format.nSamplesPerSec); + framesPerLatency = MakeFramesFromHns(pSub->period, pSub->wavexu.ext.Format.nSamplesPerSec); _CalculateAlignedPeriod(pSub, &framesPerLatency, ALIGN_BWD); } } @@ -3578,7 +3458,7 @@ static HRESULT CreateAudioClient(PaWasapiStream *pStream, PaWasapiSubStream *pSu pSub->period = MAX_BUFFER_POLL_DURATION; // Recalculate aligned period - framesPerLatency = MakeFramesFromHns(pSub->period, pSub->wavex.Format.nSamplesPerSec); + framesPerLatency = MakeFramesFromHns(pSub->period, pSub->wavexu.ext.Format.nSamplesPerSec); _CalculateAlignedPeriod(pSub, &framesPerLatency, ALIGN_BWD); } } @@ -3594,7 +3474,7 @@ static HRESULT CreateAudioClient(PaWasapiStream *pStream, PaWasapiSubStream *pSu pSub->streamFlags, pSub->period, eventPeriodicity, - &pSub->wavex.Format, + &pSub->wavexu.ext.Format, NULL); // [Output only] Check if buffer size is the one we requested in Exclusive mode, for UAC1 USB DACs WASAPI @@ -3621,7 +3501,7 @@ static HRESULT CreateAudioClient(PaWasapiStream *pStream, PaWasapiSubStream *pSu PRINT(("WASAPI: CreateAudioClient: detected %d times larger buffer than requested, correct to match user latency\n", ratio)); // Get new aligned frames lowered by calculated ratio - framesPerLatency = MakeFramesFromHns(pSub->period / ratio, pSub->wavex.Format.nSamplesPerSec); + framesPerLatency = MakeFramesFromHns(pSub->period / ratio, pSub->wavexu.ext.Format.nSamplesPerSec); _CalculateAlignedPeriod(pSub, &framesPerLatency, ALIGN_BWD); // Make sure we are not below the minimum period @@ -3648,7 +3528,7 @@ static HRESULT CreateAudioClient(PaWasapiStream *pStream, PaWasapiSubStream *pSu pSub->streamFlags, pSub->period, eventPeriodicity, - &pSub->wavex.Format, + &pSub->wavexu.ext.Format, NULL); } } @@ -3665,7 +3545,7 @@ static HRESULT CreateAudioClient(PaWasapiStream *pStream, PaWasapiSubStream *pSu pSub->period -= (100 * 10000); // Recalculate aligned period - framesPerLatency = MakeFramesFromHns(pSub->period, pSub->wavex.Format.nSamplesPerSec); + framesPerLatency = MakeFramesFromHns(pSub->period, pSub->wavexu.ext.Format.nSamplesPerSec); _CalculateAlignedPeriod(pSub, &framesPerLatency, ALIGN_BWD); // Release the previous allocations @@ -3688,7 +3568,7 @@ static HRESULT CreateAudioClient(PaWasapiStream *pStream, PaWasapiSubStream *pSu pSub->streamFlags, pSub->period, eventPeriodicity, - &pSub->wavex.Format, + &pSub->wavexu.ext.Format, NULL); } @@ -3721,7 +3601,7 @@ static HRESULT CreateAudioClient(PaWasapiStream *pStream, PaWasapiSubStream *pSu pSub->streamFlags, pSub->period, eventPeriodicity, - &pSub->wavex.Format, + &pSub->wavexu.ext.Format, NULL); } @@ -3738,7 +3618,7 @@ static HRESULT CreateAudioClient(PaWasapiStream *pStream, PaWasapiSubStream *pSu IAudioClient_AddRef(pSub->clientParent); // Recalculate buffers count - _RecalculateBuffersCount(pSub, userFramesPerBuffer, MakeFramesFromHns(pSub->period, pSub->wavex.Format.nSamplesPerSec), + _RecalculateBuffersCount(pSub, userFramesPerBuffer, MakeFramesFromHns(pSub->period, pSub->wavexu.ext.Format.nSamplesPerSec), fullDuplex, output); // No error, client is successfully created @@ -3766,7 +3646,7 @@ static PaError ActivateAudioClient(PaWasapiStream *stream, BOOL output) LogPaError(result); goto error; } - LogWAVEFORMATEXTENSIBLE(&subStream->wavex); + LogWAVEFORMATEXTENSIBLE(&subStream->wavexu.ext); // Get max possible buffer size to check if it is not less than that we request if (FAILED(hr = IAudioClient_GetBufferSize(subStream->clientParent, &maxBufferSize))) @@ -3799,7 +3679,7 @@ static PaError ActivateAudioClient(PaWasapiStream *stream, BOOL output) subStream->framesPerHostCallback : subStream->params.frames_per_buffer); // Calculate buffer latency - bufferLatency = (PaTime)maxBufferSize / subStream->wavex.Format.nSamplesPerSec; + bufferLatency = (PaTime)maxBufferSize / subStream->wavexu.ext.Format.nSamplesPerSec; // Append buffer latency to interface latency in shared mode (see GetStreamLatency notes) subStream->latencySeconds = bufferLatency; @@ -3945,7 +3825,7 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi, stream->in.streamFlags = 0; // polling interface is implemented for full-duplex mode also // Use built-in PCM converter (channel count and sample rate) if requested - if ((GetWindowsVersion() >= WINDOWS_7_SERVER2008R2) && + if ((PaWinUtil_GetOsVersion() >= paOsVersionWindows7Server2008R2) && (stream->in.shareMode == AUDCLNT_SHAREMODE_SHARED) && ((inputStreamInfo != NULL) && (inputStreamInfo->flags & paWinWasapiAutoConvert))) stream->in.streamFlags |= (AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM | AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY); @@ -3972,7 +3852,7 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi, } // Get closest format - hostInputSampleFormat = PaUtil_SelectClosestAvailableFormat(WaveToPaFormat(&stream->in.wavex), inputSampleFormat); + hostInputSampleFormat = PaUtil_SelectClosestAvailableFormat(WaveToPaFormat(&stream->in.wavexu.ext), inputSampleFormat); // Set user-side custom host processor if ((inputStreamInfo != NULL) && @@ -3995,7 +3875,7 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi, if (stream->in.params.blocking == TRUE) { UINT32 bufferFrames = ALIGN_NEXT_POW2((stream->in.framesPerHostCallback / WASAPI_PACKETS_PER_INPUT_BUFFER) * 2); - UINT32 frameSize = stream->in.wavex.Format.nBlockAlign; + UINT32 frameSize = stream->in.wavexu.ext.Format.nBlockAlign; // buffer if ((stream->in.tailBuffer = PaUtil_AllocateZeroInitializedMemory(sizeof(PaUtilRingBuffer))) == NULL) @@ -4083,7 +3963,7 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi, stream->out.streamFlags = 0; // polling interface is implemented for full-duplex mode also // Use built-in PCM converter (channel count and sample rate) if requested - if ((GetWindowsVersion() >= WINDOWS_7_SERVER2008R2) && + if ((PaWinUtil_GetOsVersion() >= paOsVersionWindows7Server2008R2) && (stream->out.shareMode == AUDCLNT_SHAREMODE_SHARED) && ((outputStreamInfo != NULL) && (outputStreamInfo->flags & paWinWasapiAutoConvert))) stream->out.streamFlags |= (AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM | AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY); @@ -4105,7 +3985,7 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi, } // Get closest format - hostOutputSampleFormat = PaUtil_SelectClosestAvailableFormat(WaveToPaFormat(&stream->out.wavex), outputSampleFormat); + hostOutputSampleFormat = PaUtil_SelectClosestAvailableFormat(WaveToPaFormat(&stream->out.wavexu.ext), outputSampleFormat); // Set user-side custom host processor if ((outputStreamInfo != NULL) && @@ -4836,7 +4716,7 @@ static PaError ReadStream( PaStream* s, void *_buffer, unsigned long frames ) { UINT32 sleep_frames = (frames < stream->in.framesPerHostCallback ? frames : stream->in.framesPerHostCallback); - sleep = GetFramesSleepTime(sleep_frames, stream->in.wavex.Format.nSamplesPerSec); + sleep = GetFramesSleepTime(sleep_frames, stream->in.wavexu.ext.Format.nSamplesPerSec); sleep /= 4; // wait only for 1/4 of the buffer // WASAPI input provides packets, thus expiring packet will result in bad audio @@ -4886,7 +4766,7 @@ static PaError ReadStream( PaStream* s, void *_buffer, unsigned long frames ) // Save tail into buffer if ((frames == 0) && (available > processed)) { - UINT32 bytes_processed = processed * stream->in.wavex.Format.nBlockAlign; + UINT32 bytes_processed = processed * stream->in.wavexu.ext.Format.nBlockAlign; UINT32 frames_to_save = available - processed; PaUtil_WriteRingBuffer(stream->in.tailBuffer, wasapi_buffer + bytes_processed, frames_to_save); @@ -4964,7 +4844,7 @@ static PaError WriteStream( PaStream* s, const void *_buffer, unsigned long fram { UINT32 sleep_frames = (frames < stream->out.framesPerHostCallback ? frames : stream->out.framesPerHostCallback); - sleep = GetFramesSleepTime(sleep_frames, stream->out.wavex.Format.nSamplesPerSec); + sleep = GetFramesSleepTime(sleep_frames, stream->out.wavexu.ext.Format.nSamplesPerSec); sleep /= 2; // wait only for half of the buffer // Avoid busy waiting, schedule next 1 millesecond wait @@ -5102,7 +4982,7 @@ static void WaspiHostProcessingLoop( void *inputBuffer, long inputFrames, { PaTime pending_time; if ((hr = IAudioClient_GetCurrentPadding(stream->in.clientProc, &pending)) == S_OK) - pending_time = (PaTime)pending / (PaTime)stream->in.wavex.Format.nSamplesPerSec; + pending_time = (PaTime)pending / (PaTime)stream->in.wavexu.ext.Format.nSamplesPerSec; else pending_time = (PaTime)stream->in.latencySeconds; @@ -5113,7 +4993,7 @@ static void WaspiHostProcessingLoop( void *inputBuffer, long inputFrames, { PaTime pending_time; if ((hr = IAudioClient_GetCurrentPadding(stream->out.clientProc, &pending)) == S_OK) - pending_time = (PaTime)pending / (PaTime)stream->out.wavex.Format.nSamplesPerSec; + pending_time = (PaTime)pending / (PaTime)stream->out.wavexu.ext.Format.nSamplesPerSec; else pending_time = (PaTime)stream->out.latencySeconds; @@ -5748,7 +5628,7 @@ static HRESULT ProcessOutputBuffer(PaWasapiStream *stream, PaWasapiHostProcessor if (stream->out.monoMixer != NULL) { // Expand buffer - UINT32 monoFrames = frames * (stream->out.wavex.Format.wBitsPerSample / 8); + UINT32 monoFrames = frames * (stream->out.wavexu.ext.Format.wBitsPerSample / 8); if (monoFrames > stream->out.monoBufferSize) { stream->out.monoBuffer = PaWasapi_ReallocateMemory(stream->out.monoBuffer, (stream->out.monoBufferSize = monoFrames)); @@ -5812,7 +5692,7 @@ static HRESULT ProcessInputBuffer(PaWasapiStream *stream, PaWasapiHostProcessor if (stream->in.monoMixer != NULL) { // expand buffer - UINT32 monoFrames = frames * (stream->in.wavex.Format.wBitsPerSample / 8); + UINT32 monoFrames = frames * (stream->in.wavexu.ext.Format.wBitsPerSample / 8); if (monoFrames > stream->in.monoBufferSize) { stream->in.monoBuffer = PaWasapi_ReallocateMemory(stream->in.monoBuffer, (stream->in.monoBufferSize = monoFrames)); @@ -6152,8 +6032,8 @@ static UINT32 ConfigureLoopSleepTimeAndScheduler(PaWasapiStream *stream, ThreadI } // Calculate timeout for the next polling attempt - sleepTimeIn = GetFramesSleepTime(userFramesIn, stream->in.wavex.Format.nSamplesPerSec); - sleepTimeOut = GetFramesSleepTime(userFramesOut, stream->out.wavex.Format.nSamplesPerSec); + sleepTimeIn = GetFramesSleepTime(userFramesIn, stream->in.wavexu.ext.Format.nSamplesPerSec); + sleepTimeOut = GetFramesSleepTime(userFramesOut, stream->out.wavexu.ext.Format.nSamplesPerSec); // WASAPI input packets tend to expire very easily, let's limit sleep time to 2 milliseconds // for all cases. Please propose better solution if any @@ -6165,8 +6045,8 @@ static UINT32 ConfigureLoopSleepTimeAndScheduler(PaWasapiStream *stream, ThreadI // Make sure not 0, othervise use ThreadIdleScheduler to bounce between [0, 1] ms to avoid too busy loop if (sleepTime == 0) { - sleepTimeIn = GetFramesSleepTimeMicroseconds(userFramesIn, stream->in.wavex.Format.nSamplesPerSec); - sleepTimeOut = GetFramesSleepTimeMicroseconds(userFramesOut, stream->out.wavex.Format.nSamplesPerSec); + sleepTimeIn = GetFramesSleepTimeMicroseconds(userFramesIn, stream->in.wavexu.ext.Format.nSamplesPerSec); + sleepTimeOut = GetFramesSleepTimeMicroseconds(userFramesOut, stream->out.wavexu.ext.Format.nSamplesPerSec); sleepTime = GetSleepTime(stream, sleepTimeIn, sleepTimeOut, userFramesOut); @@ -6476,7 +6356,7 @@ PA_THREAD_FUNC ProcThreadPoll(void *param) // convert output mono if (stream->out.monoMixer) { - UINT32 mono_frames_size = o_processed * (stream->out.wavex.Format.wBitsPerSample / 8); + UINT32 mono_frames_size = o_processed * (stream->out.wavexu.ext.Format.wBitsPerSample / 8); // expand buffer if (mono_frames_size > stream->out.monoBufferSize) { @@ -6500,7 +6380,7 @@ PA_THREAD_FUNC ProcThreadPoll(void *param) // convert input mono if (stream->in.monoMixer) { - UINT32 mono_frames_size = i_processed * (stream->in.wavex.Format.wBitsPerSample / 8); + UINT32 mono_frames_size = i_processed * (stream->in.wavexu.ext.Format.wBitsPerSample / 8); // expand buffer if (mono_frames_size > stream->in.monoBufferSize) { diff --git a/3rdparty/portaudio/src/hostapi/wdmks/pa_win_wdmks.c b/3rdparty/portaudio/src/hostapi/wdmks/pa_win_wdmks.c index 340fc2fe460c0..36cb396a8ab7b 100644 --- a/3rdparty/portaudio/src/hostapi/wdmks/pa_win_wdmks.c +++ b/3rdparty/portaudio/src/hostapi/wdmks/pa_win_wdmks.c @@ -83,6 +83,7 @@ of a device for the duration of active stream using those devices #include "pa_ringbuffer.h" #include "pa_trace.h" #include "pa_win_waveformat.h" +#include "pa_win_version.h" #include "pa_win_wdmks.h" @@ -163,12 +164,17 @@ Default is to use the pin category. #define DYNAMIC_GUID(data) {data} #define _NTRTL_ /* Turn off default definition of DEFINE_GUIDEX */ #undef DEFINE_GUID +#ifdef DECLSPEC_SELECTANY +#define PA_DECLSPEC_SELECTANY DECLSPEC_SELECTANY +#else +#define PA_DECLSPEC_SELECTANY +#endif #if defined(__clang__) || (defined(_MSVC_TRADITIONAL) && !_MSVC_TRADITIONAL) /* clang-cl and new msvc preprocessor: avoid too many arguments error */ - #define DEFINE_GUID(n, ...) EXTERN_C const GUID n = {__VA_ARGS__} + #define DEFINE_GUID(n, ...) EXTERN_C const GUID PA_DECLSPEC_SELECTANY n = {__VA_ARGS__} #define DEFINE_GUID_THUNK(n, ...) DEFINE_GUID(n, __VA_ARGS__) #define DEFINE_GUIDEX(n) DEFINE_GUID_THUNK(n, STATIC_##n) #else - #define DEFINE_GUID(n, data) EXTERN_C const GUID n = {data} + #define DEFINE_GUID(n, data) EXTERN_C const GUID PA_DECLSPEC_SELECTANY n = {data} #define DEFINE_GUID_THUNK(n, data) DEFINE_GUID(n, data) #define DEFINE_GUIDEX(n) DEFINE_GUID_THUNK(n, STATIC_##n) #endif /* __clang__, !_MSVC_TRADITIONAL */ @@ -649,29 +655,9 @@ static BOOL IsDeviceTheSame(const PaWinWdmDeviceInfo* pDev1, static BOOL IsEarlierThanVista() { -/* -NOTE: GetVersionEx() is deprecated as of Windows 8.1 and can not be used to reliably detect -versions of Windows higher than Windows 8 (due to manifest requirements for reporting higher versions). -Microsoft recommends switching to VerifyVersionInfo (available on Win 2k and later), however GetVersionEx -is faster, for now we just disable the deprecation warning. -See: https://msdn.microsoft.com/en-us/library/windows/desktop/ms724451(v=vs.85).aspx -See: http://www.codeproject.com/Articles/678606/Part-Overcoming-Windows-s-deprecation-of-GetVe -*/ -#pragma warning (disable : 4996) /* use of GetVersionEx */ - - OSVERSIONINFO osvi; - osvi.dwOSVersionInfoSize = sizeof(osvi); - if (GetVersionEx(&osvi) && osvi.dwMajorVersion<6) - { - return TRUE; - } - return FALSE; - -#pragma warning (default : 4996) + return (PaWinUtil_GetOsVersion() < paOsVersionWindowsVistaServer2008); } - - static void MemoryBarrierDummy(void) { /* Do nothing */ diff --git a/3rdparty/portaudio/src/hostapi/wmme/pa_win_wmme.c b/3rdparty/portaudio/src/hostapi/wmme/pa_win_wmme.c index 0f205eec83437..1ef5edc70e664 100644 --- a/3rdparty/portaudio/src/hostapi/wmme/pa_win_wmme.c +++ b/3rdparty/portaudio/src/hostapi/wmme/pa_win_wmme.c @@ -113,6 +113,7 @@ #include "pa_win_wmme.h" #include "pa_win_waveformat.h" #include "pa_win_util.h" +#include "pa_win_version.h" #ifdef PAWIN_USE_WDMKS_DEVICE_INFO #include "pa_win_wdmks_utils.h" @@ -321,6 +322,11 @@ static void PaMme_SetLastSystemError( DWORD errorCode ) PaMme_SetLastSystemError( errorCode ) +static int PaMme_IsWindowsVistaOrGreater() { + return (PaWinUtil_GetOsVersion() >= paOsVersionWindowsVistaServer2008); +} + + /* PaError returning wrappers for some commonly used win32 functions note that we allow passing a null ptr to have no effect. */ @@ -901,37 +907,22 @@ static PaError InitializeOutputDeviceInfo( PaWinMmeHostApiRepresentation *winMme static void GetDefaultLatencies( PaTime *defaultLowLatency, PaTime *defaultHighLatency ) { -/* -NOTE: GetVersionEx() is deprecated as of Windows 8.1 and can not be used to reliably detect -versions of Windows higher than Windows 8 (due to manifest requirements for reporting higher versions). -Microsoft recommends switching to VerifyVersionInfo (available on Win 2k and later), however GetVersionEx -is faster, for now we just disable the deprecation warning. -See: https://msdn.microsoft.com/en-us/library/windows/desktop/ms724451(v=vs.85).aspx -See: http://www.codeproject.com/Articles/678606/Part-Overcoming-Windows-s-deprecation-of-GetVe -*/ -#pragma warning (disable : 4996) /* use of GetVersionEx */ + PaOsVersion version = PaWinUtil_GetOsVersion(); - OSVERSIONINFO osvi; - osvi.dwOSVersionInfoSize = sizeof( osvi ); - GetVersionEx( &osvi ); - - /* Check for NT */ - if( (osvi.dwMajorVersion == 4) && (osvi.dwPlatformId == 2) ) + if(version <= paOsVersionWindows9x) { - *defaultLowLatency = PA_MME_WIN_NT_DEFAULT_LATENCY_; + *defaultLowLatency = PA_MME_WIN_9X_DEFAULT_LATENCY_; } - else if(osvi.dwMajorVersion >= 5) + else if(version == paOsVersionWindowsNT4) { - *defaultLowLatency = PA_MME_WIN_WDM_DEFAULT_LATENCY_; + *defaultLowLatency = PA_MME_WIN_NT_DEFAULT_LATENCY_; } - else + else if(version >= paOsVersionWindows2000) { - *defaultLowLatency = PA_MME_WIN_9X_DEFAULT_LATENCY_; + *defaultLowLatency = PA_MME_WIN_WDM_DEFAULT_LATENCY_; } *defaultHighLatency = *defaultLowLatency * 2; - -#pragma warning (default : 4996) } @@ -1771,7 +1762,7 @@ static void InitializeSingleDirectionHandlesAndBuffers( PaWinMmeSingleDirectionH static PaError InitializeWaveHandles( PaWinMmeHostApiRepresentation *winMmeHostApi, PaWinMmeSingleDirectionHandlesAndBuffers *handlesAndBuffers, unsigned long winMmeSpecificFlags, - unsigned long bytesPerHostSample, + PaSampleFormat hostSampleFormat, double sampleRate, PaWinMmeDeviceAndChannelCount *devices, unsigned int deviceCount, PaWinWaveFormatChannelMask channelMask, int isInput ); static PaError TerminateWaveHandles( PaWinMmeSingleDirectionHandlesAndBuffers *handlesAndBuffers, int isInput, int currentlyProcessingAnError ); @@ -1796,14 +1787,13 @@ static void InitializeSingleDirectionHandlesAndBuffers( PaWinMmeSingleDirectionH static PaError InitializeWaveHandles( PaWinMmeHostApiRepresentation *winMmeHostApi, PaWinMmeSingleDirectionHandlesAndBuffers *handlesAndBuffers, unsigned long winMmeSpecificFlags, - unsigned long bytesPerHostSample, + PaSampleFormat hostSampleFormat, double sampleRate, PaWinMmeDeviceAndChannelCount *devices, unsigned int deviceCount, PaWinWaveFormatChannelMask channelMask, int isInput ) { PaError result; MMRESULT mmresult; signed int i, j; - PaSampleFormat sampleFormat; int waveFormatTag; /* for error cleanup we expect that InitializeSingleDirectionHandlesAndBuffers() @@ -1832,9 +1822,7 @@ static PaError InitializeWaveHandles( PaWinMmeHostApiRepresentation *winMmeHostA ((HWAVEOUT*)handlesAndBuffers->waveHandles)[i] = 0; } - /* @todo at the moment we only use 16 bit sample format */ - sampleFormat = paInt16; - waveFormatTag = SampleFormatAndWinWmmeSpecificFlagsToLinearWaveFormatTag( sampleFormat, winMmeSpecificFlags ); + waveFormatTag = SampleFormatAndWinWmmeSpecificFlagsToLinearWaveFormatTag( hostSampleFormat, winMmeSpecificFlags ); for( i = 0; i < (signed int)deviceCount; ++i ) { @@ -1852,14 +1840,14 @@ static PaError InitializeWaveHandles( PaWinMmeHostApiRepresentation *winMmeHostA if this fails we fall back to WAVEFORMATEX */ PaWin_InitializeWaveFormatExtensible( &waveFormat, devices[i].channelCount, - sampleFormat, waveFormatTag, sampleRate, channelMask ); + hostSampleFormat, waveFormatTag, sampleRate, channelMask ); break; case 1: /* retry with WAVEFORMATEX */ PaWin_InitializeWaveFormatEx( &waveFormat, devices[i].channelCount, - sampleFormat, waveFormatTag, sampleRate ); + hostSampleFormat, waveFormatTag, sampleRate ); break; } @@ -2329,6 +2317,13 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi, unsigned long outputDeviceCount = 0; /* contains all devices and channel counts as local host api ids, even when PaWinMmeUseMultipleDevices is not used */ char throttleProcessingThreadOnOverload = 1; + /* On Windows Vista and greater, MME will accept any format that can be represented + in WAVEFORMATEX and will internally convert if and when necessary. + On older Windows versions, the story is less clear, so we restrict ourselves to + Int16 for maximum compatibility. + */ + const PaSampleFormat kNativeFormats = PaMme_IsWindowsVistaOrGreater() ? + paUInt8 | paInt16 | paInt24 | paInt32 | paFloat32 : paInt16; if( inputParameters ) { @@ -2356,7 +2351,7 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi, if( result != paNoError ) return result; hostInputSampleFormat = - PaUtil_SelectClosestAvailableFormat( paInt16 /* native formats */, inputSampleFormat ); + PaUtil_SelectClosestAvailableFormat( kNativeFormats, inputSampleFormat ); if( inputDeviceCount != 1 ){ /* always use direct speakers when using multi-device multichannel mode */ @@ -2406,7 +2401,7 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi, if( result != paNoError ) return result; hostOutputSampleFormat = - PaUtil_SelectClosestAvailableFormat( paInt16 /* native formats */, outputSampleFormat ); + PaUtil_SelectClosestAvailableFormat( kNativeFormats, outputSampleFormat ); if( outputDeviceCount != 1 ){ /* always use direct speakers when using multi-device multichannel mode */ @@ -2549,7 +2544,7 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi, { result = InitializeWaveHandles( winMmeHostApi, &stream->input, winMmeSpecificInputFlags, - stream->bufferProcessor.bytesPerHostInputSample, sampleRate, + hostInputSampleFormat, sampleRate, inputDevices, inputDeviceCount, inputChannelMask, 1 /* isInput */ ); if( result != paNoError ) goto error; } @@ -2558,7 +2553,7 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi, { result = InitializeWaveHandles( winMmeHostApi, &stream->output, winMmeSpecificOutputFlags, - stream->bufferProcessor.bytesPerHostOutputSample, sampleRate, + hostOutputSampleFormat, sampleRate, outputDevices, outputDeviceCount, outputChannelMask, 0 /* isInput */ ); if( result != paNoError ) goto error; } diff --git a/3rdparty/portaudio/src/os/unix/pa_unix_hostapis.c b/3rdparty/portaudio/src/os/unix/pa_unix_hostapis.c index 85fefb267e61e..95cd89b9d5ff0 100644 --- a/3rdparty/portaudio/src/os/unix/pa_unix_hostapis.c +++ b/3rdparty/portaudio/src/os/unix/pa_unix_hostapis.c @@ -43,6 +43,7 @@ #include "pa_hostapi.h" PaError PaJack_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex index ); +PaError PaPulseAudio_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex index ); PaError PaAlsa_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex index ); PaError PaOSS_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex index ); PaError PaAudioIO_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex index ); @@ -100,6 +101,10 @@ PaUtilHostApiInitializer *paHostApiInitializers[] = PaMacCore_Initialize, #endif +#if PA_USE_PULSEAUDIO + PaPulseAudio_Initialize, +#endif + #if PA_USE_SKELETON PaSkeleton_Initialize, #endif diff --git a/3rdparty/portaudio/src/os/win/pa_win_version.c b/3rdparty/portaudio/src/os/win/pa_win_version.c new file mode 100644 index 0000000000000..d9cd0a15c07be --- /dev/null +++ b/3rdparty/portaudio/src/os/win/pa_win_version.c @@ -0,0 +1,242 @@ +/* + * $Id$ + * Portable Audio I/O Library + * Win32 platform-specific support functions + * + * Based on the Open Source API proposed by Ross Bencina + * Copyright (c) 1999-2008 Ross Bencina + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/* + * The text above constitutes the entire PortAudio license; however, + * the PortAudio community also makes the following non-binding requests: + * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. It is also + * requested that these non-binding requests be included along with the + * license above. + */ + +/** @file + @ingroup win_src + + @brief Portable implementation of Windows OS version getter. +*/ + +#include + +#include "pa_win_version.h" +#include "pa_debugprint.h" + +// WinRT +#if defined(WINAPI_FAMILY) && (WINAPI_FAMILY == WINAPI_FAMILY_APP) + #define PA_WINRT +#endif + +// Alternative way for checking Windows version, allows to check version of Windows 8.1 and up +#ifndef PA_WINRT +static BOOL IsWindowsVersionOrGreater(WORD wMajorVersion, WORD wMinorVersion, WORD wServicePackMajor) +{ + typedef ULONGLONG (NTAPI *LPFN_VERSETCONDITIONMASK)(ULONGLONG ConditionMask, DWORD TypeMask, BYTE Condition); + typedef BOOL (WINAPI *LPFN_VERIFYVERSIONINFO)(LPOSVERSIONINFOEXA lpVersionInformation, DWORD dwTypeMask, DWORDLONG dwlConditionMask); + + LPFN_VERSETCONDITIONMASK fnVerSetConditionMask; + LPFN_VERIFYVERSIONINFO fnVerifyVersionInfo; + OSVERSIONINFOEXA osvi = { sizeof(osvi), 0, 0, 0, 0, {0}, 0, 0 }; + DWORDLONG dwlConditionMask; + + fnVerSetConditionMask = (LPFN_VERSETCONDITIONMASK)GetProcAddress(GetModuleHandleA("kernel32"), "VerSetConditionMask"); + fnVerifyVersionInfo = (LPFN_VERIFYVERSIONINFO)GetProcAddress(GetModuleHandleA("kernel32"), "VerifyVersionInfoA"); + + if ((fnVerSetConditionMask == NULL) || (fnVerifyVersionInfo == NULL)) + return FALSE; + + dwlConditionMask = fnVerSetConditionMask( + fnVerSetConditionMask( + fnVerSetConditionMask( + 0, VER_MAJORVERSION, VER_GREATER_EQUAL), + VER_MINORVERSION, VER_GREATER_EQUAL), + VER_SERVICEPACKMAJOR, VER_GREATER_EQUAL); + + osvi.dwMajorVersion = wMajorVersion; + osvi.dwMinorVersion = wMinorVersion; + osvi.wServicePackMajor = wServicePackMajor; + + return (fnVerifyVersionInfo(&osvi, VER_MAJORVERSION | VER_MINORVERSION | VER_SERVICEPACKMAJOR, dwlConditionMask) != FALSE); +} +#endif + +static PaOsVersion GetOsVersion() +{ + PaOsVersion version = paOsVersionWindowsUnknown; + +#ifndef PA_WINRT + DWORD dwMajorVersion = 0xFFFFFFFFU, dwMinorVersion = 0, dwPlatformId = 0, dwBuild = 0; + + // Can be missing in some MinGW distributions +#ifndef NT_SUCCESS + typedef LONG NTSTATUS; +#endif + +#ifndef VER_PLATFORM_WIN32_NT + #define VER_PLATFORM_WIN32_NT 2 +#endif + + // RTL_OSVERSIONINFOW equals OSVERSIONINFOW but it is missing inb MinGW winnt.h header, + // thus use OSVERSIONINFOW for greater portability + typedef NTSTATUS (WINAPI *LPFN_RTLGETVERSION)(POSVERSIONINFOW lpVersionInformation); + LPFN_RTLGETVERSION fnRtlGetVersion; + + #define NTSTATUS_SUCCESS ((NTSTATUS)0x00000000L) + + // RtlGetVersion is able to provide true Windows version (Windows 10 may be reported as Windows 8 + // by GetVersion API) unless Windows is running app in Compatibility mode when shim is replacing + // real Windows version (https://techcommunity.microsoft.com/t5/ask-the-performance-team/demystifying-shims-or-using-the-app-compat-toolkit-to-make-your/ba-p/374947). + // In our case we obey Compatibility mode and do not try to bypass it. + if ((fnRtlGetVersion = (LPFN_RTLGETVERSION)GetProcAddress(GetModuleHandleA("ntdll"), "RtlGetVersion")) != NULL) + { + OSVERSIONINFOW ver = { sizeof(OSVERSIONINFOW), 0, 0, 0, 0, {0} }; + + if (fnRtlGetVersion(&ver) == NTSTATUS_SUCCESS) + { + dwMajorVersion = ver.dwMajorVersion; + dwMinorVersion = ver.dwMinorVersion; + dwPlatformId = ver.dwPlatformId; + dwBuild = ver.dwBuildNumber; + } + + PA_DEBUG(("getting Windows version with RtlGetVersion(): major=%d, minor=%d, build=%d, platform=%d\n", + dwMajorVersion, dwMinorVersion, dwBuild, dwPlatformId)); + } + + #undef NTSTATUS_SUCCESS + + // Fallback to GetVersion if RtlGetVersion is missing + if (dwMajorVersion == 0xFFFFFFFFU) + { + typedef DWORD (WINAPI *LPFN_GETVERSION)(VOID); + LPFN_GETVERSION fnGetVersion; + + if ((fnGetVersion = (LPFN_GETVERSION)GetProcAddress(GetModuleHandleA("kernel32"), "GetVersion")) != NULL) + { + DWORD dwVersion = fnGetVersion(); + + dwMajorVersion = (DWORD)(LOBYTE(LOWORD(dwVersion))); + dwMinorVersion = (DWORD)(HIBYTE(LOWORD(dwVersion))); + dwPlatformId = VER_PLATFORM_WIN32_NT; + + if (dwVersion < 0x80000000) + dwBuild = (DWORD)(HIWORD(dwVersion)); + + PA_DEBUG(("getting Windows version with GetVersion(): major=%d, minor=%d, build=%d, platform=%d\n", + dwMajorVersion, dwMinorVersion, dwBuild, dwPlatformId)); + } + } + + // Version numbers reference: + // https://learn.microsoft.com/en-us/windows/win32/sysinfo/operating-system-version + // https://learn.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-osversioninfoexa + if (dwMajorVersion != 0xFFFFFFFFU) + { + switch (dwMajorVersion) + { + case 0: + case 1: + case 2: + case 3: + break; // skip lower + case 4: + version = (dwPlatformId == VER_PLATFORM_WIN32_NT ? paOsVersionWindowsNT4 : paOsVersionWindows9x); + break; + case 5: + switch (dwMinorVersion) + { + case 0: version = paOsVersionWindows2000; break; + case 1: version = paOsVersionWindowsXP; break; + case 2: version = paOsVersionWindowsXPServer2003; break; + default: version = paOsVersionWindowsXPServer2003; break; // shall not happen + } + break; + case 6: + switch (dwMinorVersion) + { + case 0: version = paOsVersionWindowsVistaServer2008; break; + case 1: version = paOsVersionWindows7Server2008R2; break; + case 2: version = paOsVersionWindows8Server2012; break; + case 3: version = paOsVersionWindows8_1Server2012R2; break; + default: version = paOsVersionWindows8_1Server2012R2; break; // shall not happen + } + break; + case 10: + // note: Windows 11 can be detected by dwBuild >= 22000 + version = paOsVersionWindows10Server2016; + break; + default: + version = paOsVersionWindowsFuture; + break; + } + } + // Fallback to VerifyVersionInfo if RtlGetVersion and GetVersion are missing + else + { + PA_DEBUG(("getting Windows version with VerifyVersionInfo()\n")); + + if (IsWindowsVersionOrGreater(10, 0, 0)) + version = paOsVersionWindows10Server2016; + else + if (IsWindowsVersionOrGreater(6, 3, 0)) + version = paOsVersionWindows8_1Server2012R2; + else + if (IsWindowsVersionOrGreater(6, 2, 0)) + version = paOsVersionWindows8Server2012; + else + if (IsWindowsVersionOrGreater(6, 1, 0)) + version = paOsVersionWindows7Server2008R2; + else + if (IsWindowsVersionOrGreater(6, 0, 0)) + version = paOsVersionWindowsVistaServer2008; + else + version = paOsVersionWindowsFuture; + } +#else + #if (_WIN32_WINNT >= _WIN32_WINNT_WIN10) + version = paOsVersionWindows10Server2016; + #else + version = paOsVersionWindows8Server2012; + #endif +#endif + + PA_DEBUG(("Windows version=%d\n", version)); + + return version; +} + +PaOsVersion PaWinUtil_GetOsVersion() +{ + static PaOsVersion version = paOsVersionWindowsUnknown; + + if (version == paOsVersionWindowsUnknown) + version = GetOsVersion(); + + return version; +} diff --git a/3rdparty/portaudio/src/os/win/pa_win_version.h b/3rdparty/portaudio/src/os/win/pa_win_version.h new file mode 100644 index 0000000000000..7534ee537f892 --- /dev/null +++ b/3rdparty/portaudio/src/os/win/pa_win_version.h @@ -0,0 +1,83 @@ +#ifndef PA_WIN_VERSION_H +#define PA_WIN_VERSION_H + +/* + * $Id$ + * Portable Audio I/O Library + * Win32 platform-specific support functions + * + * Based on the Open Source API proposed by Ross Bencina + * Copyright (c) 1999-2008 Ross Bencina + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/* + * The text above constitutes the entire PortAudio license; however, + * the PortAudio community also makes the following non-binding requests: + * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. It is also + * requested that these non-binding requests be included along with the + * license above. + */ + +#include "portaudio.h" + +#ifdef __cplusplus +extern "C" +{ +#endif /* __cplusplus */ + +/** + Windows OS version. +*/ +typedef enum PaOsVersion +{ + paOsVersionWindowsUnknown = 0, + paOsVersionWindows9x, // Windows 95, Windows 98, Windows ME + paOsVersionWindowsNT4, + paOsVersionWindows2000, + paOsVersionWindowsXP, + paOsVersionWindowsXPServer2003, + paOsVersionWindowsVistaServer2008, + paOsVersionWindows7Server2008R2, + paOsVersionWindows8Server2012, + paOsVersionWindows8_1Server2012R2, + paOsVersionWindows10Server2016, + // insert subsequent Windows versions below: + // ... + // paOsVersionWindowsFuture must be the last in the list + paOsVersionWindowsFuture = 1000 +} PaOsVersion; + +/** + Get Windows OS version. + @return OS version via PaOsVersion enum. + @see PaOsVersion +*/ +PaOsVersion PaWinUtil_GetOsVersion(); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* PA_WIN_VERSION_H */ diff --git a/3rdparty/portaudio/test/CMakeLists.txt b/3rdparty/portaudio/test/CMakeLists.txt index e088978aa7680..84fbe3d7611f9 100644 --- a/3rdparty/portaudio/test/CMakeLists.txt +++ b/3rdparty/portaudio/test/CMakeLists.txt @@ -19,6 +19,8 @@ add_test(patest_hang) add_test(patest_in_overflow) if(PA_USE_WASAPI) add_test(patest_jack_wasapi) + add_test(patest_wasapi_ac3) + add_test(patest_wasapi_eac3) endif() add_test(patest_latency) add_test(patest_leftright) diff --git a/3rdparty/portaudio/test/patest_latency.c b/3rdparty/portaudio/test/patest_latency.c index 7f622c131c17e..d0e4f3e022055 100644 --- a/3rdparty/portaudio/test/patest_latency.c +++ b/3rdparty/portaudio/test/patest_latency.c @@ -93,7 +93,7 @@ static int patestCallback( const void *inputBuffer, void *outputBuffer, { paTestData *data = (paTestData*)userData; float *out = (float*)outputBuffer; - int i; + unsigned long i; (void) inputBuffer; /* Prevent unused variable warning. */ diff --git a/3rdparty/portaudio/test/patest_wasapi_ac3.c b/3rdparty/portaudio/test/patest_wasapi_ac3.c new file mode 100644 index 0000000000000..9172950e1c667 --- /dev/null +++ b/3rdparty/portaudio/test/patest_wasapi_ac3.c @@ -0,0 +1,206 @@ +/** @file patest_wasapi_ac3.c + @ingroup test_src + @brief Use WASAPI-specific interface to send raw AC3 data to a S/PDIF output. + @author Ross Bencina , Jie Ding +*/ +/* + * $Id: $ + * Portable Audio I/O Library + * WASAPI ac3 sound output test + * + * Copyright (c) 2009-2023 Ross Bencina, Jie Ding + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/* + * The text above constitutes the entire PortAudio license; however, + * the PortAudio community also makes the following non-binding requests: + * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. It is also + * requested that these non-binding requests be included along with the + * license above. + */ + +#include + +#include /* required when using pa_win_wasapi.h */ + +#include "portaudio.h" +#include "pa_win_wasapi.h" + +#define NUM_SECONDS (200) +#define SAMPLE_RATE (48000) +#define FRAMES_PER_BUFFER (64) +#define CHANNEL_COUNT (2) + +#define AC3_FILEPATH "./test_48k.ac3.spdif" + +typedef struct +{ + short *buffer; + int bufferSampleCount; + int playbackIndex; +} +paTestData; + +/* This routine will be called by the PortAudio engine when audio is needed. +** It may called at interrupt level on some machines so don't do anything +** that could mess up the system like calling malloc() or free(). +*/ +static int patestCallback( const void *inputBuffer, void *outputBuffer, + unsigned long framesPerBuffer, + const PaStreamCallbackTimeInfo* timeInfo, + PaStreamCallbackFlags statusFlags, + void *userData ) +{ + paTestData *data = (paTestData*)userData; + short *out = (short*)outputBuffer; + unsigned long i,j; + + (void) timeInfo; /* Prevent unused variable warnings. */ + (void) statusFlags; + (void) inputBuffer; + + /* stream out contents of data->buffer looping at end */ + + for( i=0; ibuffer[ data->playbackIndex++ ]; + + if( data->playbackIndex >= data->bufferSampleCount ) + data->playbackIndex = 0; /* loop at end of buffer */ + } + } + + return paContinue; +} + +/*******************************************************************/ +int main(int argc, char* argv[]) +{ + PaStreamParameters outputParameters = { 0 }; + PaWasapiStreamInfo wasapiStreamInfo = { 0 }; + PaStream *stream; + PaError err; + paTestData data; + int deviceIndex; + FILE *fp; + const char *fileName = AC3_FILEPATH; + data.buffer = NULL; + + if( argc >= 2 ) + fileName = argv[1]; + + printf( "reading spdif ac3 raw stream file %s\n", fileName ); + + fp = fopen( fileName, "rb" ); + if( !fp ){ + fprintf( stderr, "error opening spdif ac3 file.\n" ); + return -1; + } + /* get file size */ + fseek( fp, 0, SEEK_END ); + data.bufferSampleCount = ftell( fp ) / sizeof(short); + fseek( fp, 0, SEEK_SET ); + + /* allocate buffer, read the whole file into memory */ + data.buffer = (short*)malloc( data.bufferSampleCount * sizeof(short) ); + if( !data.buffer ){ + fprintf( stderr, "error allocating buffer.\n" ); + return -1; + } + + fread( data.buffer, sizeof(short), data.bufferSampleCount, fp ); + fclose( fp ); + + data.playbackIndex = 0; + + err = Pa_Initialize(); + if( err != paNoError ) goto error; + + deviceIndex = Pa_GetHostApiInfo( Pa_HostApiTypeIdToHostApiIndex(paWASAPI) )->defaultOutputDevice; + if( argc >= 3 ){ + sscanf( argv[1], "%d", &deviceIndex ); + } + + printf( "using device id %d (%s)\n", deviceIndex, Pa_GetDeviceInfo(deviceIndex)->name ); + + + outputParameters.device = deviceIndex; + outputParameters.channelCount = CHANNEL_COUNT; + outputParameters.sampleFormat = paInt16; + outputParameters.suggestedLatency = Pa_GetDeviceInfo( outputParameters.device )->defaultLowOutputLatency; + outputParameters.hostApiSpecificStreamInfo = &wasapiStreamInfo; + + wasapiStreamInfo.size = sizeof(PaWasapiStreamInfo); + wasapiStreamInfo.hostApiType = paWASAPI; + wasapiStreamInfo.version = 1; + wasapiStreamInfo.flags = paWinWasapiExclusive | paWinWasapiUseChannelMask | paWinWasapiPassthrough; + wasapiStreamInfo.channelMask = PAWIN_SPEAKER_STEREO; + + wasapiStreamInfo.passthrough.formatId = ePassthroughFormatDolbyDigital; + + if( Pa_IsFormatSupported( 0, &outputParameters, SAMPLE_RATE ) == paFormatIsSupported ){ + printf( "Pa_IsFormatSupported reports device will support %d channels.\n", CHANNEL_COUNT ); + }else{ + printf( "Pa_IsFormatSupported reports device will not support %d channels.\n", CHANNEL_COUNT ); + } + + err = Pa_OpenStream( + &stream, + NULL, /* no input */ + &outputParameters, + SAMPLE_RATE, + FRAMES_PER_BUFFER, + 0, + patestCallback, + &data ); + if( err != paNoError ) goto error; + + err = Pa_StartStream( stream ); + if( err != paNoError ) goto error; + + printf("Play for %d seconds.\n", NUM_SECONDS ); + Pa_Sleep( NUM_SECONDS * 1000 ); + + err = Pa_StopStream( stream ); + if( err != paNoError ) goto error; + + err = Pa_CloseStream( stream ); + if( err != paNoError ) goto error; + + Pa_Terminate(); + free( data.buffer ); + printf("Test finished.\n"); + + return err; +error: + Pa_Terminate(); + free( data.buffer ); + + fprintf( stderr, "An error occurred while using the portaudio stream\n" ); + fprintf( stderr, "Error number: %d\n", err ); + fprintf( stderr, "Error message: %s\n", Pa_GetErrorText( err ) ); + return err; +} diff --git a/3rdparty/portaudio/test/patest_wasapi_eac3.c b/3rdparty/portaudio/test/patest_wasapi_eac3.c new file mode 100644 index 0000000000000..566df7a236cf0 --- /dev/null +++ b/3rdparty/portaudio/test/patest_wasapi_eac3.c @@ -0,0 +1,208 @@ +/** @file patest_wasapi_eac3.c + @ingroup test_src + @brief Use WASAPI-specific interface to send raw EAC3 data to a S/PDIF output. + @author Ross Bencina , Jie Ding +*/ +/* + * $Id: $ + * Portable Audio I/O Library + * WASAPI eac3 sound output test + * + * Copyright (c) 2009-2023 Ross Bencina, Jie Ding + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/* + * The text above constitutes the entire PortAudio license; however, + * the PortAudio community also makes the following non-binding requests: + * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. It is also + * requested that these non-binding requests be included along with the + * license above. + */ + +#include + +#include /* required when using pa_win_wasapi.h */ + +#include "portaudio.h" +#include "pa_win_wasapi.h" + +#define NUM_SECONDS (200) +#define SAMPLE_RATE (192000) +#define FRAMES_PER_BUFFER (64) +#define CHANNEL_COUNT (2) + +#define EAC3_FILEPATH "./test_48k.eac3.spdif" + +typedef struct +{ + short *buffer; + int bufferSampleCount; + int playbackIndex; +} +paTestData; + +/* This routine will be called by the PortAudio engine when audio is needed. +** It may called at interrupt level on some machines so don't do anything +** that could mess up the system like calling malloc() or free(). +*/ +static int patestCallback( const void *inputBuffer, void *outputBuffer, + unsigned long framesPerBuffer, + const PaStreamCallbackTimeInfo* timeInfo, + PaStreamCallbackFlags statusFlags, + void *userData ) +{ + paTestData *data = (paTestData*)userData; + short *out = (short*)outputBuffer; + unsigned long i,j; + + (void) timeInfo; /* Prevent unused variable warnings. */ + (void) statusFlags; + (void) inputBuffer; + + /* stream out contents of data->buffer looping at end */ + + for( i=0; ibuffer[ data->playbackIndex++ ]; + + if( data->playbackIndex >= data->bufferSampleCount ) + data->playbackIndex = 0; /* loop at end of buffer */ + } + } + + return paContinue; +} + +/*******************************************************************/ +int main(int argc, char* argv[]) +{ + PaStreamParameters outputParameters = { 0 }; + PaWasapiStreamInfo wasapiStreamInfo = { 0 }; + PaStream *stream; + PaError err; + paTestData data; + int deviceIndex; + FILE *fp; + const char *fileName = EAC3_FILEPATH; + data.buffer = NULL; + + if( argc >= 2 ) + fileName = argv[1]; + + printf( "reading spdif eac3 raw stream file %s\n", fileName ); + + fp = fopen( fileName, "rb" ); + if( !fp ){ + fprintf( stderr, "error opening spdif eac3 file.\n" ); + return -1; + } + /* get file size */ + fseek( fp, 0, SEEK_END ); + data.bufferSampleCount = ftell( fp ) / sizeof(short); + fseek( fp, 0, SEEK_SET ); + + /* allocate buffer, read the whole file into memory */ + data.buffer = (short*)malloc( data.bufferSampleCount * sizeof(short) ); + if( !data.buffer ){ + fprintf( stderr, "error allocating buffer.\n" ); + return -1; + } + + fread( data.buffer, sizeof(short), data.bufferSampleCount, fp ); + fclose( fp ); + + data.playbackIndex = 0; + + err = Pa_Initialize(); + if( err != paNoError ) goto error; + + deviceIndex = Pa_GetHostApiInfo( Pa_HostApiTypeIdToHostApiIndex(paWASAPI) )->defaultOutputDevice; + if( argc >= 3 ){ + sscanf( argv[1], "%d", &deviceIndex ); + } + + printf( "using device id %d (%s)\n", deviceIndex, Pa_GetDeviceInfo(deviceIndex)->name ); + + + outputParameters.device = deviceIndex; + outputParameters.channelCount = CHANNEL_COUNT; + outputParameters.sampleFormat = paInt16; + outputParameters.suggestedLatency = Pa_GetDeviceInfo( outputParameters.device )->defaultLowOutputLatency; + outputParameters.hostApiSpecificStreamInfo = &wasapiStreamInfo; + + wasapiStreamInfo.size = sizeof(PaWasapiStreamInfo); + wasapiStreamInfo.hostApiType = paWASAPI; + wasapiStreamInfo.version = 1; + wasapiStreamInfo.flags = paWinWasapiExclusive | paWinWasapiUseChannelMask | paWinWasapiPassthrough; + wasapiStreamInfo.channelMask = PAWIN_SPEAKER_5POINT1; /* set channel mask according to the encoded Dolby stream */ + + wasapiStreamInfo.passthrough.formatId = ePassthroughFormatDolbyDigitalPlus; + wasapiStreamInfo.passthrough.encodedSamplesPerSec = SAMPLE_RATE / 4; + wasapiStreamInfo.passthrough.encodedChannelCount = 6; + + if( Pa_IsFormatSupported( 0, &outputParameters, SAMPLE_RATE ) == paFormatIsSupported ){ + printf( "Pa_IsFormatSupported reports device will support %d channels.\n", CHANNEL_COUNT ); + }else{ + printf( "Pa_IsFormatSupported reports device will not support %d channels.\n", CHANNEL_COUNT ); + } + + err = Pa_OpenStream( + &stream, + NULL, /* no input */ + &outputParameters, + SAMPLE_RATE, + FRAMES_PER_BUFFER, + 0, + patestCallback, + &data ); + if( err != paNoError ) goto error; + + err = Pa_StartStream( stream ); + if( err != paNoError ) goto error; + + printf("Play for %d seconds.\n", NUM_SECONDS ); + Pa_Sleep( NUM_SECONDS * 1000 ); + + err = Pa_StopStream( stream ); + if( err != paNoError ) goto error; + + err = Pa_CloseStream( stream ); + if( err != paNoError ) goto error; + + Pa_Terminate(); + free( data.buffer ); + printf("Test finished.\n"); + + return err; +error: + Pa_Terminate(); + free( data.buffer ); + + fprintf( stderr, "An error occurred while using the portaudio stream\n" ); + fprintf( stderr, "Error number: %d\n", err ); + fprintf( stderr, "Error message: %s\n", Pa_GetErrorText( err ) ); + return err; +} diff --git a/docs/source/initialsetup/compilingmame.rst b/docs/source/initialsetup/compilingmame.rst index b61b3ca6335e7..1f5e559744a46 100644 --- a/docs/source/initialsetup/compilingmame.rst +++ b/docs/source/initialsetup/compilingmame.rst @@ -758,12 +758,6 @@ to make when generating the Visual Studio project files. This stops warnings from being treated as errors. (MSVC seems to lack options to control which specific warnings are treated as error, which other compilers support.) -There is an as-yet unresolved issue with duplicate COM GUIDS being defined in -the PortAudio library when the target Windows version is set to Windows Vista -(6.0) or later. To work around this, add ``NO_USE_PORTAUDIO=1`` to the options -passed to make when generating the Visual Studio project files. MAME will be -built without support for sound output via PortAudio. - .. _compiling-unusual: diff --git a/makefile b/makefile index 40731fba66429..dea2b568d09ce 100644 --- a/makefile +++ b/makefile @@ -1163,7 +1163,7 @@ endif .PHONY: vs2019_clang vs2019_clang: generate - $(SILENT) $(GENIE) $(PARAMS) $(TARGET_PARAMS) --vs=clangcl --NO_USE_PORTAUDIO=1 vs2019 + $(SILENT) $(GENIE) $(PARAMS) $(TARGET_PARAMS) --vs=clangcl vs2019 ifdef MSBUILD $(SILENT) msbuild.exe $(PROJECTDIR_WIN)/vs2019-clang/$(PROJECT_NAME).sln $(MSBUILD_PARAMS) endif diff --git a/scripts/src/3rdparty.lua b/scripts/src/3rdparty.lua index dacb3a298acdc..2ded17d2ccffb 100755 --- a/scripts/src/3rdparty.lua +++ b/scripts/src/3rdparty.lua @@ -1550,6 +1550,13 @@ project "portaudio" "/wd4456", -- warning C4456: declaration of 'xxx' hides previous local declaration "/wd4312", -- warning C4312: 'type cast': conversion from 'UINT' to 'HWAVEIN' of greater size } + if _OPTIONS["vs"]=="clangcl" then + buildoptions { + "-Wno-implicit-const-int-float-conversion", + "-Wno-sometimes-uninitialized", + "-Wno-unused-but-set-variable", + } + end if _OPTIONS["vs"]=="intel-15" then buildoptions { "/Qwd869", -- remark #869: parameter "xxx" was never referenced @@ -1641,6 +1648,7 @@ project "portaudio" configuration { } files { MAME_DIR .. "3rdparty/portaudio/src/os/win/pa_win_util.c", + MAME_DIR .. "3rdparty/portaudio/src/os/win/pa_win_version.c", MAME_DIR .. "3rdparty/portaudio/src/os/win/pa_win_waveformat.c", MAME_DIR .. "3rdparty/portaudio/src/os/win/pa_win_hostapis.c", MAME_DIR .. "3rdparty/portaudio/src/os/win/pa_win_coinitialize.c",