early-access version 2291

This commit is contained in:
pineappleEA 2021-12-08 07:33:31 +01:00
parent 02705a572c
commit 58d4c17ba4
61 changed files with 9377 additions and 4730 deletions

View file

@ -131,7 +131,7 @@ add_definitions(-DBOOST_ASIO_DISABLE_CONCEPTS)
if (MSVC) if (MSVC)
add_compile_options($<$<COMPILE_LANGUAGE:CXX>:/std:c++latest>) add_compile_options($<$<COMPILE_LANGUAGE:CXX>:/std:c++latest>)
# cubeb and boost still make use of deprecated result_of. # boost still makes use of deprecated result_of.
add_definitions(-D_HAS_DEPRECATED_RESULT_OF) add_definitions(-D_HAS_DEPRECATED_RESULT_OF)
else() else()
set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD 20)

View file

@ -1,7 +1,7 @@
yuzu emulator early access yuzu emulator early access
============= =============
This is the source code for early-access 2289. This is the source code for early-access 2291.
## Legal Notice ## Legal Notice

13
externals/cubeb/.clang-format vendored Executable file
View file

@ -0,0 +1,13 @@
IndentWidth: 2
UseTab: Never
ReflowComments: true
PointerAlignment: Middle
AlignAfterOpenBracket: Align
AlwaysBreakAfterReturnType: TopLevel
ColumnLimit: 80
BreakBeforeBraces: Custom
BraceWrapping:
AfterFunction: true
AfterControlStatement: Never
SpaceBeforeParens: ControlStatements
BreakBeforeBinaryOperators: None

75
externals/cubeb/.github/workflows/build.yml vendored Executable file
View file

@ -0,0 +1,75 @@
name: Build
on: [push, pull_request]
jobs:
build:
runs-on: ${{ matrix.os }}
env:
BUILD_TYPE: ${{ matrix.type }}
strategy:
matrix:
os: [ubuntu-20.04, windows-2019, macos-10.15]
type: [Release, Debug]
steps:
- uses: actions/checkout@v2
with:
submodules: true
- name: Install Dependencies (Linux)
run: sudo apt-get update && sudo apt-get install libpulse-dev pulseaudio
if: matrix.os == 'ubuntu-20.04'
- name: Start Sound Server (Linux)
run: pulseaudio -D --start
if: matrix.os == 'ubuntu-20.04'
- name: Configure CMake
shell: bash
run: cmake -S . -B build -DCMAKE_BUILD_TYPE=$BUILD_TYPE
- name: Build
shell: bash
run: cmake --build build
- name: Test
shell: bash
run: (cd build && ctest -V)
if: ${{ matrix.os == 'ubuntu-20.04' || matrix.os == 'macos-10.15' }}
build-android:
runs-on: ubuntu-20.04
env:
BUILD_TYPE: ${{ matrix.type }}
strategy:
matrix:
type: [Release, Debug]
steps:
- uses: actions/checkout@v2
with:
submodules: true
- name: Configure CMake
shell: bash
run: cmake -S . -B build -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DCMAKE_TOOLCHAIN_FILE=$ANDROID_NDK_HOME/build/cmake/android.toolchain.cmake -DANDROID_NATIVE_API_LEVEL=android-26
- name: Build
shell: bash
run: cmake --build build
check_format:
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v2
with:
submodules: true
- name: Configure CMake
shell: bash
run: cmake -S . -B build
- name: Check format
shell: bash
run: cmake --build build --target clang-format-check

View file

@ -1,7 +1,7 @@
# TODO # TODO
# - backend selection via command line, rather than simply detecting headers. # - backend selection via command line, rather than simply detecting headers.
cmake_minimum_required(VERSION 3.1 FATAL_ERROR) cmake_minimum_required(VERSION 3.14 FATAL_ERROR)
project(cubeb project(cubeb
VERSION 0.0.0) VERSION 0.0.0)
@ -9,33 +9,42 @@ option(BUILD_SHARED_LIBS "Build shared libraries" OFF)
option(BUILD_TESTS "Build tests" ON) option(BUILD_TESTS "Build tests" ON)
option(BUILD_RUST_LIBS "Build rust backends" OFF) option(BUILD_RUST_LIBS "Build rust backends" OFF)
option(BUILD_TOOLS "Build tools" ON) option(BUILD_TOOLS "Build tools" ON)
option(BUNDLE_SPEEX "Bundle the speex library" OFF)
option(LAZY_LOAD_LIBS "Lazily load shared libraries" ON)
option(USE_SANITIZERS "Use sanitizers" ON)
if(NOT CMAKE_BUILD_TYPE) if(NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE "RelWithDebInfo" CACHE STRING set(CMAKE_BUILD_TYPE "RelWithDebInfo" CACHE STRING
"Choose the type of build, options are: Debug Release RelWithDebInfo MinSizeRel." FORCE) "Choose the type of build, options are: Debug Release RelWithDebInfo MinSizeRel." FORCE)
endif() endif()
if(POLICY CMP0063)
cmake_policy(SET CMP0063 NEW)
endif()
set(CMAKE_C_STANDARD 99) set(CMAKE_C_STANDARD 99)
set(CMAKE_CXX_STANDARD 11) set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_STANDARD_REQUIRED ON)
if(NOT COMMAND add_sanitizers) if(USE_SANITIZERS)
list(APPEND CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake/sanitizers-cmake/cmake")
find_package(Sanitizers)
if(NOT COMMAND add_sanitizers) if(NOT COMMAND add_sanitizers)
message(FATAL_ERROR "Could not find sanitizers-cmake: run\n\tgit submodule update --init --recursive\nin base git checkout") list(APPEND CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake/sanitizers-cmake/cmake")
find_package(Sanitizers)
if(NOT COMMAND add_sanitizers)
message(FATAL_ERROR "Could not find sanitizers-cmake: run\n\tgit submodule update --init --recursive\nin base git checkout")
endif()
endif() endif()
else()
macro(add_sanitizers UNUSED)
endmacro()
endif() endif()
if(BUILD_TESTS) if(BUILD_TESTS)
find_package(GTest QUIET)
if(TARGET GTest::Main)
add_library(gtest_main ALIAS GTest::Main)
endif()
if(NOT TARGET gtest_main) if(NOT TARGET gtest_main)
if(NOT EXISTS "${PROJECT_SOURCE_DIR}/googletest/CMakeLists.txt") if(NOT EXISTS "${PROJECT_SOURCE_DIR}/googletest/CMakeLists.txt")
message(FATAL_ERROR "Could not find googletest: run\n\tgit submodule update --init --recursive\nin base git checkout") message(FATAL_ERROR "Could not find googletest: run\n\tgit submodule update --init --recursive\nin base git checkout")
endif() endif()
add_definitions(-DGTEST_HAS_TR1_TUPLE=0) add_definitions(-DGTEST_HAS_TR1_TUPLE=0 -DGTEST_HAS_RTTI=0)
set(gtest_force_shared_crt ON CACHE BOOL "") set(gtest_force_shared_crt ON CACHE BOOL "")
add_subdirectory(googletest) add_subdirectory(googletest)
endif() endif()
@ -60,7 +69,10 @@ endif()
set(CMAKE_CXX_WARNING_LEVEL 4) set(CMAKE_CXX_WARNING_LEVEL 4)
if(NOT MSVC) if(NOT MSVC)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra -Wno-unused-parameter") set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra -Wno-unused-parameter")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Wno-unused-parameter") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Wno-unused-parameter -fno-exceptions -fno-rtti")
else()
string(REPLACE "/GR" "" CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS}) # Disable RTTI
string(REPLACE "/EHsc" "" CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS}) # Disable Exceptions
endif() endif()
add_library(cubeb add_library(cubeb
@ -70,15 +82,14 @@ add_library(cubeb
src/cubeb_log.cpp src/cubeb_log.cpp
src/cubeb_strings.c src/cubeb_strings.c
src/cubeb_utils.cpp src/cubeb_utils.cpp
$<TARGET_OBJECTS:speex>) )
target_include_directories(cubeb target_include_directories(cubeb
PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include> $<INSTALL_INTERFACE:include> PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include> $<INSTALL_INTERFACE:include>
) )
target_include_directories(cubeb PRIVATE src) set_target_properties(cubeb PROPERTIES
target_compile_definitions(cubeb PRIVATE OUTSIDE_SPEEX) VERSION ${cubeb_VERSION}
target_compile_definitions(cubeb PRIVATE FLOATING_POINT) SOVERSION ${cubeb_VERSION_MAJOR}
target_compile_definitions(cubeb PRIVATE EXPORT=) )
target_compile_definitions(cubeb PRIVATE RANDOM_PREFIX=speex)
add_sanitizers(cubeb) add_sanitizers(cubeb)
@ -88,17 +99,9 @@ target_include_directories(cubeb
PUBLIC $<BUILD_INTERFACE:${CMAKE_BINARY_DIR}/exports> PUBLIC $<BUILD_INTERFACE:${CMAKE_BINARY_DIR}/exports>
) )
if(UNIX) include(GNUInstallDirs)
include(GNUInstallDirs)
else()
set(CMAKE_INSTALL_LIBDIR "lib")
set(CMAKE_INSTALL_BINDIR "bin")
set(CMAKE_INSTALL_DATADIR "share")
set(CMAKE_INSTALL_DOCDIR "${CMAKE_INSTALL_DATADIR}/doc")
set(CMAKE_INSTALL_INCLUDEDIR "include")
endif()
install(DIRECTORY ${CMAKE_SOURCE_DIR}/include/${PROJECT_NAME} DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) install(DIRECTORY ${CMAKE_SOURCE_DIR}/include/${PROJECT_NAME} TYPE INCLUDE)
install(DIRECTORY ${CMAKE_BINARY_DIR}/exports/ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME}) install(DIRECTORY ${CMAKE_BINARY_DIR}/exports/ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME})
include(CMakePackageConfigHelpers) include(CMakePackageConfigHelpers)
@ -113,34 +116,135 @@ configure_package_config_file(
INSTALL_DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}" INSTALL_DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}"
) )
install(TARGETS cubeb
EXPORT "${PROJECT_NAME}Targets"
DESTINATION ${CMAKE_INSTALL_PREFIX}
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
)
install( install(
FILES "${PROJECT_BINARY_DIR}/${PROJECT_NAME}Config.cmake" "${PROJECT_BINARY_DIR}/${PROJECT_NAME}ConfigVersion.cmake" FILES "${PROJECT_BINARY_DIR}/${PROJECT_NAME}Config.cmake" "${PROJECT_BINARY_DIR}/${PROJECT_NAME}ConfigVersion.cmake"
DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}" DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}"
) )
install(TARGETS cubeb EXPORT "${PROJECT_NAME}Targets")
install( install(
EXPORT "${PROJECT_NAME}Targets" EXPORT "${PROJECT_NAME}Targets"
NAMESPACE "${PROJECT_NAME}::" NAMESPACE "${PROJECT_NAME}::"
DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}" DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}"
) )
add_library(speex OBJECT if(NOT BUNDLE_SPEEX)
src/speex/resample.c) find_package(PkgConfig)
set_target_properties(speex PROPERTIES POSITION_INDEPENDENT_CODE TRUE) if(PKG_CONFIG_FOUND)
target_compile_definitions(speex PRIVATE OUTSIDE_SPEEX) pkg_check_modules(speexdsp IMPORTED_TARGET speexdsp)
target_compile_definitions(speex PRIVATE FLOATING_POINT) if(speexdsp_FOUND)
target_compile_definitions(speex PRIVATE EXPORT=) add_library(speex ALIAS PkgConfig::speexdsp)
target_compile_definitions(speex PRIVATE RANDOM_PREFIX=speex) endif()
endif()
endif()
if(NOT TARGET speex)
add_library(speex STATIC subprojects/speex/resample.c)
set_target_properties(speex PROPERTIES POSITION_INDEPENDENT_CODE TRUE)
target_include_directories(speex INTERFACE subprojects)
target_compile_definitions(speex PUBLIC
OUTSIDE_SPEEX
FLOATING_POINT
EXPORT=
RANDOM_PREFIX=speex
)
endif()
# $<BUILD_INTERFACE:> required because of https://gitlab.kitware.com/cmake/cmake/-/issues/15415
target_link_libraries(cubeb PRIVATE $<BUILD_INTERFACE:speex>)
include(CheckIncludeFiles) include(CheckIncludeFiles)
# Threads needed by cubeb_log, _pulse, _alsa, _jack, _sndio, _oss and _sun
set(THREADS_PREFER_PTHREAD_FLAG ON)
find_package(Threads)
target_link_libraries(cubeb PRIVATE Threads::Threads)
if(LAZY_LOAD_LIBS)
check_include_files(pulse/pulseaudio.h USE_PULSE)
check_include_files(alsa/asoundlib.h USE_ALSA)
check_include_files(jack/jack.h USE_JACK)
check_include_files(sndio.h USE_SNDIO)
check_include_files(aaudio/AAudio.h USE_AAUDIO)
if(USE_PULSE OR USE_ALSA OR USE_JACK OR USE_SNDIO OR USE_AAUDIO)
target_link_libraries(cubeb PRIVATE ${CMAKE_DL_LIBS})
endif()
else()
find_package(PkgConfig REQUIRED)
pkg_check_modules(libpulse IMPORTED_TARGET libpulse)
if(libpulse_FOUND)
set(USE_PULSE ON)
target_compile_definitions(cubeb PRIVATE DISABLE_LIBPULSE_DLOPEN)
target_link_libraries(cubeb PRIVATE PkgConfig::libpulse)
endif()
pkg_check_modules(alsa IMPORTED_TARGET alsa)
if(alsa_FOUND)
set(USE_ALSA ON)
target_compile_definitions(cubeb PRIVATE DISABLE_LIBASOUND_DLOPEN)
target_link_libraries(cubeb PRIVATE PkgConfig::alsa)
endif()
pkg_check_modules(jack IMPORTED_TARGET jack)
if(jack_FOUND)
set(USE_JACK ON)
target_compile_definitions(cubeb PRIVATE DISABLE_LIBJACK_DLOPEN)
target_link_libraries(cubeb PRIVATE PkgConfig::jack)
endif()
check_include_files(sndio.h USE_SNDIO)
if(USE_SNDIO)
target_compile_definitions(cubeb PRIVATE DISABLE_LIBSNDIO_DLOPEN)
target_link_libraries(cubeb PRIVATE sndio)
endif()
check_include_files(aaudio/AAudio.h USE_AAUDIO)
if(USE_AAUDIO)
target_compile_definitions(cubeb PRIVATE DISABLE_LIBAAUDIO_DLOPEN)
target_link_libraries(cubeb PRIVATE aaudio)
endif()
endif()
if(USE_PULSE)
target_sources(cubeb PRIVATE src/cubeb_pulse.c)
target_compile_definitions(cubeb PRIVATE USE_PULSE)
endif()
if(USE_ALSA)
target_sources(cubeb PRIVATE src/cubeb_alsa.c)
target_compile_definitions(cubeb PRIVATE USE_ALSA)
endif()
if(USE_JACK)
target_sources(cubeb PRIVATE src/cubeb_jack.cpp)
target_compile_definitions(cubeb PRIVATE USE_JACK)
endif()
if(USE_SNDIO)
target_sources(cubeb PRIVATE src/cubeb_sndio.c)
target_compile_definitions(cubeb PRIVATE USE_SNDIO)
endif()
if(USE_AAUDIO)
target_sources(cubeb PRIVATE src/cubeb_aaudio.cpp)
target_compile_definitions(cubeb PRIVATE USE_AAUDIO)
# set this definition to enable low latency mode. Possibly bad for battery
target_compile_definitions(cubeb PRIVATE CUBEB_AAUDIO_LOW_LATENCY)
# set this definition to enable power saving mode. Possibly resulting
# in high latency
# target_compile_definitions(cubeb PRIVATE CUBEB_AAUDIO_LOW_POWER_SAVING)
# set this mode to make the backend use an exclusive stream.
# will decrease latency.
# target_compile_definitions(cubeb PRIVATE CUBEB_AAUDIO_EXCLUSIVE_STREAM)
endif()
check_include_files(AudioUnit/AudioUnit.h USE_AUDIOUNIT) check_include_files(AudioUnit/AudioUnit.h USE_AUDIOUNIT)
if(USE_AUDIOUNIT) if(USE_AUDIOUNIT)
target_sources(cubeb PRIVATE target_sources(cubeb PRIVATE
@ -150,30 +254,6 @@ if(USE_AUDIOUNIT)
target_link_libraries(cubeb PRIVATE "-framework AudioUnit" "-framework CoreAudio" "-framework CoreServices") target_link_libraries(cubeb PRIVATE "-framework AudioUnit" "-framework CoreAudio" "-framework CoreServices")
endif() endif()
check_include_files(pulse/pulseaudio.h USE_PULSE)
if(USE_PULSE)
target_sources(cubeb PRIVATE
src/cubeb_pulse.c)
target_compile_definitions(cubeb PRIVATE USE_PULSE)
target_link_libraries(cubeb PRIVATE pthread ${CMAKE_DL_LIBS})
endif()
check_include_files(alsa/asoundlib.h USE_ALSA)
if(USE_ALSA)
target_sources(cubeb PRIVATE
src/cubeb_alsa.c)
target_compile_definitions(cubeb PRIVATE USE_ALSA)
target_link_libraries(cubeb PRIVATE pthread ${CMAKE_DL_LIBS})
endif()
check_include_files(jack/jack.h USE_JACK)
if(USE_JACK)
target_sources(cubeb PRIVATE
src/cubeb_jack.cpp)
target_compile_definitions(cubeb PRIVATE USE_JACK)
target_link_libraries(cubeb PRIVATE pthread ${CMAKE_DL_LIBS})
endif()
check_include_files(audioclient.h USE_WASAPI) check_include_files(audioclient.h USE_WASAPI)
if(USE_WASAPI) if(USE_WASAPI)
target_sources(cubeb PRIVATE target_sources(cubeb PRIVATE
@ -207,7 +287,6 @@ if(HAVE_SYS_SOUNDCARD_H)
target_sources(cubeb PRIVATE target_sources(cubeb PRIVATE
src/cubeb_oss.c) src/cubeb_oss.c)
target_compile_definitions(cubeb PRIVATE USE_OSS) target_compile_definitions(cubeb PRIVATE USE_OSS)
target_link_libraries(cubeb PRIVATE pthread)
endif() endif()
endif() endif()
@ -219,20 +298,11 @@ if(USE_AUDIOTRACK)
target_link_libraries(cubeb PRIVATE log) target_link_libraries(cubeb PRIVATE log)
endif() endif()
check_include_files(sndio.h USE_SNDIO)
if(USE_SNDIO)
target_sources(cubeb PRIVATE
src/cubeb_sndio.c)
target_compile_definitions(cubeb PRIVATE USE_SNDIO)
target_link_libraries(cubeb PRIVATE pthread ${CMAKE_DL_LIBS})
endif()
check_include_files(sys/audioio.h USE_SUN) check_include_files(sys/audioio.h USE_SUN)
if(USE_SUN) if(USE_SUN)
target_sources(cubeb PRIVATE target_sources(cubeb PRIVATE
src/cubeb_sun.c) src/cubeb_sun.c)
target_compile_definitions(cubeb PRIVATE USE_SUN) target_compile_definitions(cubeb PRIVATE USE_SUN)
target_link_libraries(cubeb PRIVATE pthread)
endif() endif()
check_include_files(kai.h USE_KAI) check_include_files(kai.h USE_KAI)
@ -295,12 +365,11 @@ if(BUILD_TESTS)
macro(cubeb_add_test NAME) macro(cubeb_add_test NAME)
add_executable(test_${NAME} test/test_${NAME}.cpp) add_executable(test_${NAME} test/test_${NAME}.cpp)
target_include_directories(test_${NAME} PRIVATE ${gtest_SOURCE_DIR}/include) target_include_directories(test_${NAME} PRIVATE ${gtest_SOURCE_DIR}/include src)
target_include_directories(test_${NAME} PRIVATE src)
target_link_libraries(test_${NAME} PRIVATE cubeb gtest_main) target_link_libraries(test_${NAME} PRIVATE cubeb gtest_main)
add_test(${NAME} test_${NAME}) add_test(${NAME} test_${NAME})
add_sanitizers(test_${NAME}) add_sanitizers(test_${NAME})
install(TARGETS test_${NAME} DESTINATION ${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_BINDIR}) install(TARGETS test_${NAME})
endmacro(cubeb_add_test) endmacro(cubeb_add_test)
cubeb_add_test(sanity) cubeb_add_test(sanity)
@ -310,17 +379,12 @@ if(BUILD_TESTS)
cubeb_add_test(devices) cubeb_add_test(devices)
cubeb_add_test(callback_ret) cubeb_add_test(callback_ret)
add_executable(test_resampler test/test_resampler.cpp src/cubeb_resampler.cpp $<TARGET_OBJECTS:speex>) add_executable(test_resampler test/test_resampler.cpp src/cubeb_resampler.cpp src/cubeb_log.cpp)
target_include_directories(test_resampler PRIVATE ${gtest_SOURCE_DIR}/include) target_include_directories(test_resampler PRIVATE ${gtest_SOURCE_DIR}/include src)
target_include_directories(test_resampler PRIVATE src) target_link_libraries(test_resampler PRIVATE cubeb gtest_main speex)
target_compile_definitions(test_resampler PRIVATE OUTSIDE_SPEEX)
target_compile_definitions(test_resampler PRIVATE FLOATING_POINT)
target_compile_definitions(test_resampler PRIVATE EXPORT=)
target_compile_definitions(test_resampler PRIVATE RANDOM_PREFIX=speex)
target_link_libraries(test_resampler PRIVATE cubeb gtest_main)
add_test(resampler test_resampler) add_test(resampler test_resampler)
add_sanitizers(test_resampler) add_sanitizers(test_resampler)
install(TARGETS test_resampler DESTINATION ${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_BINDIR}) install(TARGETS test_resampler)
cubeb_add_test(duplex) cubeb_add_test(duplex)
@ -342,5 +406,17 @@ if(BUILD_TOOLS)
target_include_directories(cubeb-test PRIVATE src) target_include_directories(cubeb-test PRIVATE src)
target_link_libraries(cubeb-test PRIVATE cubeb) target_link_libraries(cubeb-test PRIVATE cubeb)
add_sanitizers(cubeb-test) add_sanitizers(cubeb-test)
install(TARGETS cubeb-test DESTINATION ${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_BINDIR}) install(TARGETS cubeb-test)
endif() endif()
add_custom_target(clang-format-check
find
${CMAKE_CURRENT_SOURCE_DIR}/src
${CMAKE_CURRENT_SOURCE_DIR}/include
-type f (-name "*.cpp" -o -name "*.c" -o -name "*.h")
-not -path "*/subprojects/speex/*"
-print0
| xargs -0 clang-format -Werror -n
COMMENT "Check formatting with clang-format"
VERBATIM)

View file

@ -1,5 +1,4 @@
[![Build Status](https://travis-ci.org/kinetiknz/cubeb.svg?branch=master)](https://travis-ci.org/kinetiknz/cubeb) [![Build Status](https://github.com/mozilla/cubeb/actions/workflows/build.yml/badge.svg)](https://github.com/mozilla/cubeb/actions/workflows/build.yml)
[![Build status](https://ci.appveyor.com/api/projects/status/osv2r0m1j1nt9csr/branch/master?svg=true)](https://ci.appveyor.com/project/kinetiknz/cubeb/branch/master)
See INSTALL.md for build instructions. See INSTALL.md for build instructions.

View file

@ -7,9 +7,9 @@
#if !defined(CUBEB_c2f983e9_c96f_e71c_72c3_bbf62992a382) #if !defined(CUBEB_c2f983e9_c96f_e71c_72c3_bbf62992a382)
#define CUBEB_c2f983e9_c96f_e71c_72c3_bbf62992a382 #define CUBEB_c2f983e9_c96f_e71c_72c3_bbf62992a382
#include "cubeb_export.h"
#include <stdint.h> #include <stdint.h>
#include <stdlib.h> #include <stdlib.h>
#include "cubeb_export.h"
#if defined(__cplusplus) #if defined(__cplusplus)
extern "C" { extern "C" {
@ -122,8 +122,10 @@ extern "C" {
/** @file /** @file
The <tt>libcubeb</tt> C API. */ The <tt>libcubeb</tt> C API. */
typedef struct cubeb cubeb; /**< Opaque handle referencing the application state. */ typedef struct cubeb
typedef struct cubeb_stream cubeb_stream; /**< Opaque handle referencing the stream state. */ cubeb; /**< Opaque handle referencing the application state. */
typedef struct cubeb_stream
cubeb_stream; /**< Opaque handle referencing the stream state. */
/** Sample format enumeration. */ /** Sample format enumeration. */
typedef enum { typedef enum {
@ -155,8 +157,10 @@ typedef void const * cubeb_devid;
/** Level (verbosity) of logging for a particular cubeb context. */ /** Level (verbosity) of logging for a particular cubeb context. */
typedef enum { typedef enum {
CUBEB_LOG_DISABLED = 0, /** < Logging disabled */ CUBEB_LOG_DISABLED = 0, /** < Logging disabled */
CUBEB_LOG_NORMAL = 1, /**< Logging lifetime operation (creation/destruction). */ CUBEB_LOG_NORMAL =
CUBEB_LOG_VERBOSE = 2, /**< Verbose logging of callbacks, can have performance implications. */ 1, /**< Logging lifetime operation (creation/destruction). */
CUBEB_LOG_VERBOSE = 2, /**< Verbose logging of callbacks, can have performance
implications. */
} cubeb_log_level; } cubeb_log_level;
typedef enum { typedef enum {
@ -190,10 +194,10 @@ enum {
CUBEB_LAYOUT_STEREO = CHANNEL_FRONT_LEFT | CHANNEL_FRONT_RIGHT, CUBEB_LAYOUT_STEREO = CHANNEL_FRONT_LEFT | CHANNEL_FRONT_RIGHT,
CUBEB_LAYOUT_STEREO_LFE = CUBEB_LAYOUT_STEREO | CHANNEL_LOW_FREQUENCY, CUBEB_LAYOUT_STEREO_LFE = CUBEB_LAYOUT_STEREO | CHANNEL_LOW_FREQUENCY,
CUBEB_LAYOUT_3F = CUBEB_LAYOUT_3F =
CHANNEL_FRONT_LEFT | CHANNEL_FRONT_RIGHT | CHANNEL_FRONT_CENTER, CHANNEL_FRONT_LEFT | CHANNEL_FRONT_RIGHT | CHANNEL_FRONT_CENTER,
CUBEB_LAYOUT_3F_LFE = CUBEB_LAYOUT_3F | CHANNEL_LOW_FREQUENCY, CUBEB_LAYOUT_3F_LFE = CUBEB_LAYOUT_3F | CHANNEL_LOW_FREQUENCY,
CUBEB_LAYOUT_2F1 = CUBEB_LAYOUT_2F1 =
CHANNEL_FRONT_LEFT | CHANNEL_FRONT_RIGHT | CHANNEL_BACK_CENTER, CHANNEL_FRONT_LEFT | CHANNEL_FRONT_RIGHT | CHANNEL_BACK_CENTER,
CUBEB_LAYOUT_2F1_LFE = CUBEB_LAYOUT_2F1 | CHANNEL_LOW_FREQUENCY, CUBEB_LAYOUT_2F1_LFE = CUBEB_LAYOUT_2F1 | CHANNEL_LOW_FREQUENCY,
CUBEB_LAYOUT_3F1 = CHANNEL_FRONT_LEFT | CHANNEL_FRONT_RIGHT | CUBEB_LAYOUT_3F1 = CHANNEL_FRONT_LEFT | CHANNEL_FRONT_RIGHT |
CHANNEL_FRONT_CENTER | CHANNEL_BACK_CENTER, CHANNEL_FRONT_CENTER | CHANNEL_BACK_CENTER,
@ -222,46 +226,50 @@ enum {
/** Miscellaneous stream preferences. */ /** Miscellaneous stream preferences. */
typedef enum { typedef enum {
CUBEB_STREAM_PREF_NONE = 0x00, /**< No stream preferences are requested. */ CUBEB_STREAM_PREF_NONE = 0x00, /**< No stream preferences are requested. */
CUBEB_STREAM_PREF_LOOPBACK = 0x01, /**< Request a loopback stream. Should be CUBEB_STREAM_PREF_LOOPBACK =
specified on the input params and an 0x01, /**< Request a loopback stream. Should be
output device to loopback from should specified on the input params and an
be passed in place of an input device. */ output device to loopback from should
be passed in place of an input device. */
CUBEB_STREAM_PREF_DISABLE_DEVICE_SWITCHING = 0x02, /**< Disable switching CUBEB_STREAM_PREF_DISABLE_DEVICE_SWITCHING = 0x02, /**< Disable switching
default device on OS default device on OS
changes. */ changes. */
CUBEB_STREAM_PREF_VOICE = 0x04, /**< This stream is going to transport voice data. CUBEB_STREAM_PREF_VOICE =
Depending on the backend and platform, this can 0x04, /**< This stream is going to transport voice data.
change the audio input or output devices Depending on the backend and platform, this can
selected, as well as the quality of the stream, change the audio input or output devices
for example to accomodate bluetooth SCO modes on selected, as well as the quality of the stream,
bluetooth devices. */ for example to accomodate bluetooth SCO modes on
CUBEB_STREAM_PREF_RAW = 0x08, /**< Windows only. Bypass all signal processing bluetooth devices. */
except for always on APO, driver and hardware. */ CUBEB_STREAM_PREF_RAW =
CUBEB_STREAM_PREF_PERSIST = 0x10, /**< Request that the volume and mute settings 0x08, /**< Windows only. Bypass all signal processing
should persist across restarts of the stream except for always on APO, driver and hardware. */
and/or application. May not be honored for CUBEB_STREAM_PREF_PERSIST = 0x10, /**< Request that the volume and mute
all backends and platforms. */ settings should persist across restarts
of the stream and/or application. This is
CUBEB_STREAM_PREF_JACK_NO_AUTO_CONNECT = 0x20 /**< Don't automatically try to connect obsolete and ignored by all backends. */
ports. Only affects the jack CUBEB_STREAM_PREF_JACK_NO_AUTO_CONNECT = 0x20 /**< Don't automatically try to
backend. */ connect ports. Only affects
the jack backend. */
} cubeb_stream_prefs; } cubeb_stream_prefs;
/** Stream format initialization parameters. */ /** Stream format initialization parameters. */
typedef struct { typedef struct {
cubeb_sample_format format; /**< Requested sample format. One of cubeb_sample_format format; /**< Requested sample format. One of
#cubeb_sample_format. */ #cubeb_sample_format. */
uint32_t rate; /**< Requested sample rate. Valid range is [1000, 192000]. */ uint32_t rate; /**< Requested sample rate. Valid range is [1000, 192000]. */
uint32_t channels; /**< Requested channel count. Valid range is [1, 8]. */ uint32_t channels; /**< Requested channel count. Valid range is [1, 8]. */
cubeb_channel_layout layout; /**< Requested channel layout. This must be consistent with the provided channels. CUBEB_LAYOUT_UNDEFINED if unknown */ cubeb_channel_layout
cubeb_stream_prefs prefs; /**< Requested preferences. */ layout; /**< Requested channel layout. This must be consistent with the
provided channels. CUBEB_LAYOUT_UNDEFINED if unknown */
cubeb_stream_prefs prefs; /**< Requested preferences. */
} cubeb_stream_params; } cubeb_stream_params;
/** Audio device description */ /** Audio device description */
typedef struct { typedef struct {
char * output_name; /**< The name of the output device */ char * output_name; /**< The name of the output device */
char * input_name; /**< The name of the input device */ char * input_name; /**< The name of the input device */
} cubeb_device; } cubeb_device;
/** Stream states signaled via state_callback. */ /** Stream states signaled via state_callback. */
@ -274,12 +282,15 @@ typedef enum {
/** Result code enumeration. */ /** Result code enumeration. */
enum { enum {
CUBEB_OK = 0, /**< Success. */ CUBEB_OK = 0, /**< Success. */
CUBEB_ERROR = -1, /**< Unclassified error. */ CUBEB_ERROR = -1, /**< Unclassified error. */
CUBEB_ERROR_INVALID_FORMAT = -2, /**< Unsupported #cubeb_stream_params requested. */ CUBEB_ERROR_INVALID_FORMAT =
-2, /**< Unsupported #cubeb_stream_params requested. */
CUBEB_ERROR_INVALID_PARAMETER = -3, /**< Invalid parameter specified. */ CUBEB_ERROR_INVALID_PARAMETER = -3, /**< Invalid parameter specified. */
CUBEB_ERROR_NOT_SUPPORTED = -4, /**< Optional function not implemented in current backend. */ CUBEB_ERROR_NOT_SUPPORTED =
CUBEB_ERROR_DEVICE_UNAVAILABLE = -5 /**< Device specified by #cubeb_devid not available. */ -4, /**< Optional function not implemented in current backend. */
CUBEB_ERROR_DEVICE_UNAVAILABLE =
-5 /**< Device specified by #cubeb_devid not available. */
}; };
/** /**
@ -295,50 +306,56 @@ typedef enum {
* The state of a device. * The state of a device.
*/ */
typedef enum { typedef enum {
CUBEB_DEVICE_STATE_DISABLED, /**< The device has been disabled at the system level. */ CUBEB_DEVICE_STATE_DISABLED, /**< The device has been disabled at the system
CUBEB_DEVICE_STATE_UNPLUGGED, /**< The device is enabled, but nothing is plugged into it. */ level. */
CUBEB_DEVICE_STATE_ENABLED /**< The device is enabled. */ CUBEB_DEVICE_STATE_UNPLUGGED, /**< The device is enabled, but nothing is
plugged into it. */
CUBEB_DEVICE_STATE_ENABLED /**< The device is enabled. */
} cubeb_device_state; } cubeb_device_state;
/** /**
* Architecture specific sample type. * Architecture specific sample type.
*/ */
typedef enum { typedef enum {
CUBEB_DEVICE_FMT_S16LE = 0x0010, /**< 16-bit integers, Little Endian. */ CUBEB_DEVICE_FMT_S16LE = 0x0010, /**< 16-bit integers, Little Endian. */
CUBEB_DEVICE_FMT_S16BE = 0x0020, /**< 16-bit integers, Big Endian. */ CUBEB_DEVICE_FMT_S16BE = 0x0020, /**< 16-bit integers, Big Endian. */
CUBEB_DEVICE_FMT_F32LE = 0x1000, /**< 32-bit floating point, Little Endian. */ CUBEB_DEVICE_FMT_F32LE = 0x1000, /**< 32-bit floating point, Little Endian. */
CUBEB_DEVICE_FMT_F32BE = 0x2000 /**< 32-bit floating point, Big Endian. */ CUBEB_DEVICE_FMT_F32BE = 0x2000 /**< 32-bit floating point, Big Endian. */
} cubeb_device_fmt; } cubeb_device_fmt;
#if defined(WORDS_BIGENDIAN) || defined(__BIG_ENDIAN__) #if defined(WORDS_BIGENDIAN) || defined(__BIG_ENDIAN__)
/** 16-bit integers, native endianess, when on a Big Endian environment. */ /** 16-bit integers, native endianess, when on a Big Endian environment. */
#define CUBEB_DEVICE_FMT_S16NE CUBEB_DEVICE_FMT_S16BE #define CUBEB_DEVICE_FMT_S16NE CUBEB_DEVICE_FMT_S16BE
/** 32-bit floating points, native endianess, when on a Big Endian environment. */ /** 32-bit floating points, native endianess, when on a Big Endian environment.
#define CUBEB_DEVICE_FMT_F32NE CUBEB_DEVICE_FMT_F32BE */
#define CUBEB_DEVICE_FMT_F32NE CUBEB_DEVICE_FMT_F32BE
#else #else
/** 16-bit integers, native endianess, when on a Little Endian environment. */ /** 16-bit integers, native endianess, when on a Little Endian environment. */
#define CUBEB_DEVICE_FMT_S16NE CUBEB_DEVICE_FMT_S16LE #define CUBEB_DEVICE_FMT_S16NE CUBEB_DEVICE_FMT_S16LE
/** 32-bit floating points, native endianess, when on a Little Endian /** 32-bit floating points, native endianess, when on a Little Endian
* environment. */ * environment. */
#define CUBEB_DEVICE_FMT_F32NE CUBEB_DEVICE_FMT_F32LE #define CUBEB_DEVICE_FMT_F32NE CUBEB_DEVICE_FMT_F32LE
#endif #endif
/** All the 16-bit integers types. */ /** All the 16-bit integers types. */
#define CUBEB_DEVICE_FMT_S16_MASK (CUBEB_DEVICE_FMT_S16LE | CUBEB_DEVICE_FMT_S16BE) #define CUBEB_DEVICE_FMT_S16_MASK \
(CUBEB_DEVICE_FMT_S16LE | CUBEB_DEVICE_FMT_S16BE)
/** All the 32-bit floating points types. */ /** All the 32-bit floating points types. */
#define CUBEB_DEVICE_FMT_F32_MASK (CUBEB_DEVICE_FMT_F32LE | CUBEB_DEVICE_FMT_F32BE) #define CUBEB_DEVICE_FMT_F32_MASK \
(CUBEB_DEVICE_FMT_F32LE | CUBEB_DEVICE_FMT_F32BE)
/** All the device formats types. */ /** All the device formats types. */
#define CUBEB_DEVICE_FMT_ALL (CUBEB_DEVICE_FMT_S16_MASK | CUBEB_DEVICE_FMT_F32_MASK) #define CUBEB_DEVICE_FMT_ALL \
(CUBEB_DEVICE_FMT_S16_MASK | CUBEB_DEVICE_FMT_F32_MASK)
/** Channel type for a `cubeb_stream`. Depending on the backend and platform /** Channel type for a `cubeb_stream`. Depending on the backend and platform
* used, this can control inter-stream interruption, ducking, and volume * used, this can control inter-stream interruption, ducking, and volume
* control. * control.
*/ */
typedef enum { typedef enum {
CUBEB_DEVICE_PREF_NONE = 0x00, CUBEB_DEVICE_PREF_NONE = 0x00,
CUBEB_DEVICE_PREF_MULTIMEDIA = 0x01, CUBEB_DEVICE_PREF_MULTIMEDIA = 0x01,
CUBEB_DEVICE_PREF_VOICE = 0x02, CUBEB_DEVICE_PREF_VOICE = 0x02,
CUBEB_DEVICE_PREF_NOTIFICATION = 0x04, CUBEB_DEVICE_PREF_NOTIFICATION = 0x04,
CUBEB_DEVICE_PREF_ALL = 0x0F CUBEB_DEVICE_PREF_ALL = 0x0F
} cubeb_device_pref; } cubeb_device_pref;
/** This structure holds the characteristics /** This structure holds the characteristics
@ -347,25 +364,30 @@ typedef enum {
* `cubeb_device_collection` and must be destroyed via * `cubeb_device_collection` and must be destroyed via
* `cubeb_device_collection_destroy`. */ * `cubeb_device_collection_destroy`. */
typedef struct { typedef struct {
cubeb_devid devid; /**< Device identifier handle. */ cubeb_devid devid; /**< Device identifier handle. */
char const * device_id; /**< Device identifier which might be presented in a UI. */ char const *
char const * friendly_name; /**< Friendly device name which might be presented in a UI. */ device_id; /**< Device identifier which might be presented in a UI. */
char const * group_id; /**< Two devices have the same group identifier if they belong to the same physical device; for example a headset and microphone. */ char const * friendly_name; /**< Friendly device name which might be presented
char const * vendor_name; /**< Optional vendor name, may be NULL. */ in a UI. */
char const * group_id; /**< Two devices have the same group identifier if they
belong to the same physical device; for example a
headset and microphone. */
char const * vendor_name; /**< Optional vendor name, may be NULL. */
cubeb_device_type type; /**< Type of device (Input/Output). */ cubeb_device_type type; /**< Type of device (Input/Output). */
cubeb_device_state state; /**< State of device disabled/enabled/unplugged. */ cubeb_device_state state; /**< State of device disabled/enabled/unplugged. */
cubeb_device_pref preferred;/**< Preferred device. */ cubeb_device_pref preferred; /**< Preferred device. */
cubeb_device_fmt format; /**< Sample format supported. */ cubeb_device_fmt format; /**< Sample format supported. */
cubeb_device_fmt default_format; /**< The default sample format for this device. */ cubeb_device_fmt
uint32_t max_channels; /**< Channels. */ default_format; /**< The default sample format for this device. */
uint32_t default_rate; /**< Default/Preferred sample rate. */ uint32_t max_channels; /**< Channels. */
uint32_t max_rate; /**< Maximum sample rate supported. */ uint32_t default_rate; /**< Default/Preferred sample rate. */
uint32_t min_rate; /**< Minimum sample rate supported. */ uint32_t max_rate; /**< Maximum sample rate supported. */
uint32_t min_rate; /**< Minimum sample rate supported. */
uint32_t latency_lo; /**< Lowest possible latency in frames. */ uint32_t latency_lo; /**< Lowest possible latency in frames. */
uint32_t latency_hi; /**< Higest possible latency in frames. */ uint32_t latency_hi; /**< Higest possible latency in frames. */
} cubeb_device_info; } cubeb_device_info;
/** Device collection. /** Device collection.
@ -398,34 +420,32 @@ typedef struct {
being stopped. being stopped.
@retval CUBEB_ERROR on error, in which case the data callback will stop @retval CUBEB_ERROR on error, in which case the data callback will stop
and the stream will enter a shutdown state. */ and the stream will enter a shutdown state. */
typedef long (* cubeb_data_callback)(cubeb_stream * stream, typedef long (*cubeb_data_callback)(cubeb_stream * stream, void * user_ptr,
void * user_ptr, void const * input_buffer,
void const * input_buffer, void * output_buffer, long nframes);
void * output_buffer,
long nframes);
/** User supplied state callback. /** User supplied state callback.
@param stream The stream for this this callback fired. @param stream The stream for this this callback fired.
@param user_ptr The pointer passed to cubeb_stream_init. @param user_ptr The pointer passed to cubeb_stream_init.
@param state The new state of the stream. */ @param state The new state of the stream. */
typedef void (* cubeb_state_callback)(cubeb_stream * stream, typedef void (*cubeb_state_callback)(cubeb_stream * stream, void * user_ptr,
void * user_ptr, cubeb_state state);
cubeb_state state);
/** /**
* User supplied callback called when the underlying device changed. * User supplied callback called when the underlying device changed.
* @param user The pointer passed to cubeb_stream_init. */ * @param user The pointer passed to cubeb_stream_init. */
typedef void (* cubeb_device_changed_callback)(void * user_ptr); typedef void (*cubeb_device_changed_callback)(void * user_ptr);
/** /**
* User supplied callback called when the underlying device collection changed. * User supplied callback called when the underlying device collection changed.
* @param context A pointer to the cubeb context. * @param context A pointer to the cubeb context.
* @param user_ptr The pointer passed to cubeb_register_device_collection_changed. */ * @param user_ptr The pointer passed to
typedef void (* cubeb_device_collection_changed_callback)(cubeb * context, * cubeb_register_device_collection_changed. */
void * user_ptr); typedef void (*cubeb_device_collection_changed_callback)(cubeb * context,
void * user_ptr);
/** User supplied callback called when a message needs logging. */ /** User supplied callback called when a message needs logging. */
typedef void (* cubeb_log_callback)(char const * fmt, ...); typedef void (*cubeb_log_callback)(char const * fmt, ...);
/** Initialize an application context. This will perform any library or /** Initialize an application context. This will perform any library or
application scoped initialization. application scoped initialization.
@ -445,13 +465,15 @@ typedef void (* cubeb_log_callback)(char const * fmt, ...);
@retval CUBEB_OK in case of success. @retval CUBEB_OK in case of success.
@retval CUBEB_ERROR in case of error, for example because the host @retval CUBEB_ERROR in case of error, for example because the host
has no audio hardware. */ has no audio hardware. */
CUBEB_EXPORT int cubeb_init(cubeb ** context, char const * context_name, CUBEB_EXPORT int
char const * backend_name); cubeb_init(cubeb ** context, char const * context_name,
char const * backend_name);
/** Get a read-only string identifying this context's current backend. /** Get a read-only string identifying this context's current backend.
@param context A pointer to the cubeb context. @param context A pointer to the cubeb context.
@retval Read-only string identifying current backend. */ @retval Read-only string identifying current backend. */
CUBEB_EXPORT char const * cubeb_get_backend_id(cubeb * context); CUBEB_EXPORT char const *
cubeb_get_backend_id(cubeb * context);
/** Get the maximum possible number of channels. /** Get the maximum possible number of channels.
@param context A pointer to the cubeb context. @param context A pointer to the cubeb context.
@ -460,7 +482,8 @@ CUBEB_EXPORT char const * cubeb_get_backend_id(cubeb * context);
@retval CUBEB_ERROR_INVALID_PARAMETER @retval CUBEB_ERROR_INVALID_PARAMETER
@retval CUBEB_ERROR_NOT_SUPPORTED @retval CUBEB_ERROR_NOT_SUPPORTED
@retval CUBEB_ERROR */ @retval CUBEB_ERROR */
CUBEB_EXPORT int cubeb_get_max_channel_count(cubeb * context, uint32_t * max_channels); CUBEB_EXPORT int
cubeb_get_max_channel_count(cubeb * context, uint32_t * max_channels);
/** Get the minimal latency value, in frames, that is guaranteed to work /** Get the minimal latency value, in frames, that is guaranteed to work
when creating a stream for the specified sample rate. This is platform, when creating a stream for the specified sample rate. This is platform,
@ -473,9 +496,9 @@ CUBEB_EXPORT int cubeb_get_max_channel_count(cubeb * context, uint32_t * max_cha
@retval CUBEB_OK @retval CUBEB_OK
@retval CUBEB_ERROR_INVALID_PARAMETER @retval CUBEB_ERROR_INVALID_PARAMETER
@retval CUBEB_ERROR_NOT_SUPPORTED */ @retval CUBEB_ERROR_NOT_SUPPORTED */
CUBEB_EXPORT int cubeb_get_min_latency(cubeb * context, CUBEB_EXPORT int
cubeb_stream_params * params, cubeb_get_min_latency(cubeb * context, cubeb_stream_params * params,
uint32_t * latency_frames); uint32_t * latency_frames);
/** Get the preferred sample rate for this backend: this is hardware and /** Get the preferred sample rate for this backend: this is hardware and
platform dependent, and can avoid resampling, and/or trigger fastpaths. platform dependent, and can avoid resampling, and/or trigger fastpaths.
@ -484,12 +507,14 @@ CUBEB_EXPORT int cubeb_get_min_latency(cubeb * context,
@retval CUBEB_OK @retval CUBEB_OK
@retval CUBEB_ERROR_INVALID_PARAMETER @retval CUBEB_ERROR_INVALID_PARAMETER
@retval CUBEB_ERROR_NOT_SUPPORTED */ @retval CUBEB_ERROR_NOT_SUPPORTED */
CUBEB_EXPORT int cubeb_get_preferred_sample_rate(cubeb * context, uint32_t * rate); CUBEB_EXPORT int
cubeb_get_preferred_sample_rate(cubeb * context, uint32_t * rate);
/** Destroy an application context. This must be called after all stream have /** Destroy an application context. This must be called after all stream have
* been destroyed. * been destroyed.
@param context A pointer to the cubeb context.*/ @param context A pointer to the cubeb context.*/
CUBEB_EXPORT void cubeb_destroy(cubeb * context); CUBEB_EXPORT void
cubeb_destroy(cubeb * context);
/** Initialize a stream associated with the supplied application context. /** Initialize a stream associated with the supplied application context.
@param context A pointer to the cubeb context. @param context A pointer to the cubeb context.
@ -497,19 +522,21 @@ CUBEB_EXPORT void cubeb_destroy(cubeb * context);
cubeb stream. cubeb stream.
@param stream_name A name for this stream. @param stream_name A name for this stream.
@param input_device Device for the input side of the stream. If NULL the @param input_device Device for the input side of the stream. If NULL the
default input device is used. Passing a valid cubeb_devid default input device is used. Passing a valid
means the stream only ever uses that device. Passing a NULL cubeb_devid means the stream only ever uses that device. Passing a NULL
cubeb_devid allows the stream to follow that device type's cubeb_devid allows the stream to follow that device
OS default. type's OS default.
@param input_stream_params Parameters for the input side of the stream, or @param input_stream_params Parameters for the input side of the stream, or
NULL if this stream is output only. NULL if this stream is output only.
@param output_device Device for the output side of the stream. If NULL the @param output_device Device for the output side of the stream. If NULL the
default output device is used. Passing a valid cubeb_devid default output device is used. Passing a valid
means the stream only ever uses that device. Passing a NULL cubeb_devid means the stream only ever uses that device. Passing a NULL
cubeb_devid allows the stream to follow that device type's cubeb_devid allows the stream to follow that device
OS default. type's OS default.
@param output_stream_params Parameters for the output side of the stream, or @param output_stream_params Parameters for the output side of the stream, or
NULL if this stream is input only. NULL if this stream is input only. When input
and output stream parameters are supplied, their
rate has to be the same.
@param latency_frames Stream latency in frames. Valid range @param latency_frames Stream latency in frames. Valid range
is [1, 96000]. is [1, 96000].
@param data_callback Will be called to preroll data before playback is @param data_callback Will be called to preroll data before playback is
@ -521,49 +548,42 @@ CUBEB_EXPORT void cubeb_destroy(cubeb * context);
@retval CUBEB_ERROR @retval CUBEB_ERROR
@retval CUBEB_ERROR_INVALID_FORMAT @retval CUBEB_ERROR_INVALID_FORMAT
@retval CUBEB_ERROR_DEVICE_UNAVAILABLE */ @retval CUBEB_ERROR_DEVICE_UNAVAILABLE */
CUBEB_EXPORT int cubeb_stream_init(cubeb * context, CUBEB_EXPORT int
cubeb_stream ** stream, cubeb_stream_init(cubeb * context, cubeb_stream ** stream,
char const * stream_name, char const * stream_name, cubeb_devid input_device,
cubeb_devid input_device, cubeb_stream_params * input_stream_params,
cubeb_stream_params * input_stream_params, cubeb_devid output_device,
cubeb_devid output_device, cubeb_stream_params * output_stream_params,
cubeb_stream_params * output_stream_params, uint32_t latency_frames, cubeb_data_callback data_callback,
uint32_t latency_frames, cubeb_state_callback state_callback, void * user_ptr);
cubeb_data_callback data_callback,
cubeb_state_callback state_callback,
void * user_ptr);
/** Destroy a stream. `cubeb_stream_stop` MUST be called before destroying a /** Destroy a stream. `cubeb_stream_stop` MUST be called before destroying a
stream. stream.
@param stream The stream to destroy. */ @param stream The stream to destroy. */
CUBEB_EXPORT void cubeb_stream_destroy(cubeb_stream * stream); CUBEB_EXPORT void
cubeb_stream_destroy(cubeb_stream * stream);
/** Start playback. /** Start playback.
@param stream @param stream
@retval CUBEB_OK @retval CUBEB_OK
@retval CUBEB_ERROR */ @retval CUBEB_ERROR */
CUBEB_EXPORT int cubeb_stream_start(cubeb_stream * stream); CUBEB_EXPORT int
cubeb_stream_start(cubeb_stream * stream);
/** Stop playback. /** Stop playback.
@param stream @param stream
@retval CUBEB_OK @retval CUBEB_OK
@retval CUBEB_ERROR */ @retval CUBEB_ERROR */
CUBEB_EXPORT int cubeb_stream_stop(cubeb_stream * stream); CUBEB_EXPORT int
cubeb_stream_stop(cubeb_stream * stream);
/** Reset stream to the default device.
@param stream
@retval CUBEB_OK
@retval CUBEB_ERROR_INVALID_PARAMETER
@retval CUBEB_ERROR_NOT_SUPPORTED
@retval CUBEB_ERROR */
CUBEB_EXPORT int cubeb_stream_reset_default_device(cubeb_stream * stream);
/** Get the current stream playback position. /** Get the current stream playback position.
@param stream @param stream
@param position Playback position in frames. @param position Playback position in frames.
@retval CUBEB_OK @retval CUBEB_OK
@retval CUBEB_ERROR */ @retval CUBEB_ERROR */
CUBEB_EXPORT int cubeb_stream_get_position(cubeb_stream * stream, uint64_t * position); CUBEB_EXPORT int
cubeb_stream_get_position(cubeb_stream * stream, uint64_t * position);
/** Get the latency for this stream, in frames. This is the number of frames /** Get the latency for this stream, in frames. This is the number of frames
between the time cubeb acquires the data in the callback and the listener between the time cubeb acquires the data in the callback and the listener
@ -573,7 +593,8 @@ CUBEB_EXPORT int cubeb_stream_get_position(cubeb_stream * stream, uint64_t * pos
@retval CUBEB_OK @retval CUBEB_OK
@retval CUBEB_ERROR_NOT_SUPPORTED @retval CUBEB_ERROR_NOT_SUPPORTED
@retval CUBEB_ERROR */ @retval CUBEB_ERROR */
CUBEB_EXPORT int cubeb_stream_get_latency(cubeb_stream * stream, uint32_t * latency); CUBEB_EXPORT int
cubeb_stream_get_latency(cubeb_stream * stream, uint32_t * latency);
/** Get the input latency for this stream, in frames. This is the number of /** Get the input latency for this stream, in frames. This is the number of
frames between the time the audio input devices records the data, and they frames between the time the audio input devices records the data, and they
@ -584,7 +605,8 @@ CUBEB_EXPORT int cubeb_stream_get_latency(cubeb_stream * stream, uint32_t * late
@retval CUBEB_OK @retval CUBEB_OK
@retval CUBEB_ERROR_NOT_SUPPORTED @retval CUBEB_ERROR_NOT_SUPPORTED
@retval CUBEB_ERROR */ @retval CUBEB_ERROR */
CUBEB_EXPORT int cubeb_stream_get_input_latency(cubeb_stream * stream, uint32_t * latency); CUBEB_EXPORT int
cubeb_stream_get_input_latency(cubeb_stream * stream, uint32_t * latency);
/** Set the volume for a stream. /** Set the volume for a stream.
@param stream the stream for which to adjust the volume. @param stream the stream for which to adjust the volume.
@param volume a float between 0.0 (muted) and 1.0 (maximum volume) @param volume a float between 0.0 (muted) and 1.0 (maximum volume)
@ -592,7 +614,8 @@ CUBEB_EXPORT int cubeb_stream_get_input_latency(cubeb_stream * stream, uint32_t
@retval CUBEB_ERROR_INVALID_PARAMETER volume is outside [0.0, 1.0] or @retval CUBEB_ERROR_INVALID_PARAMETER volume is outside [0.0, 1.0] or
stream is an invalid pointer stream is an invalid pointer
@retval CUBEB_ERROR_NOT_SUPPORTED */ @retval CUBEB_ERROR_NOT_SUPPORTED */
CUBEB_EXPORT int cubeb_stream_set_volume(cubeb_stream * stream, float volume); CUBEB_EXPORT int
cubeb_stream_set_volume(cubeb_stream * stream, float volume);
/** Change a stream's name. /** Change a stream's name.
@param stream the stream for which to set the name. @param stream the stream for which to set the name.
@ -600,7 +623,8 @@ CUBEB_EXPORT int cubeb_stream_set_volume(cubeb_stream * stream, float volume);
@retval CUBEB_OK @retval CUBEB_OK
@retval CUBEB_ERROR_INVALID_PARAMETER if any pointer is invalid @retval CUBEB_ERROR_INVALID_PARAMETER if any pointer is invalid
@retval CUBEB_ERROR_NOT_SUPPORTED */ @retval CUBEB_ERROR_NOT_SUPPORTED */
CUBEB_EXPORT int cubeb_stream_set_name(cubeb_stream * stream, char const * stream_name); CUBEB_EXPORT int
cubeb_stream_set_name(cubeb_stream * stream, char const * stream_name);
/** Get the current output device for this stream. /** Get the current output device for this stream.
@param stm the stream for which to query the current output device @param stm the stream for which to query the current output device
@ -609,8 +633,9 @@ CUBEB_EXPORT int cubeb_stream_set_name(cubeb_stream * stream, char const * strea
@retval CUBEB_ERROR_INVALID_PARAMETER if either stm, device or count are @retval CUBEB_ERROR_INVALID_PARAMETER if either stm, device or count are
invalid pointers invalid pointers
@retval CUBEB_ERROR_NOT_SUPPORTED */ @retval CUBEB_ERROR_NOT_SUPPORTED */
CUBEB_EXPORT int cubeb_stream_get_current_device(cubeb_stream * stm, CUBEB_EXPORT int
cubeb_device ** const device); cubeb_stream_get_current_device(cubeb_stream * stm,
cubeb_device ** const device);
/** Destroy a cubeb_device structure. /** Destroy a cubeb_device structure.
@param stream the stream passed in cubeb_stream_get_current_device @param stream the stream passed in cubeb_stream_get_current_device
@ -618,8 +643,8 @@ CUBEB_EXPORT int cubeb_stream_get_current_device(cubeb_stream * stm,
@retval CUBEB_OK in case of success @retval CUBEB_OK in case of success
@retval CUBEB_ERROR_INVALID_PARAMETER if devices is an invalid pointer @retval CUBEB_ERROR_INVALID_PARAMETER if devices is an invalid pointer
@retval CUBEB_ERROR_NOT_SUPPORTED */ @retval CUBEB_ERROR_NOT_SUPPORTED */
CUBEB_EXPORT int cubeb_stream_device_destroy(cubeb_stream * stream, CUBEB_EXPORT int
cubeb_device * devices); cubeb_stream_device_destroy(cubeb_stream * stream, cubeb_device * devices);
/** Set a callback to be notified when the output device changes. /** Set a callback to be notified when the output device changes.
@param stream the stream for which to set the callback. @param stream the stream for which to set the callback.
@ -629,32 +654,38 @@ CUBEB_EXPORT int cubeb_stream_device_destroy(cubeb_stream * stream,
@retval CUBEB_ERROR_INVALID_PARAMETER if either stream or @retval CUBEB_ERROR_INVALID_PARAMETER if either stream or
device_changed_callback are invalid pointers. device_changed_callback are invalid pointers.
@retval CUBEB_ERROR_NOT_SUPPORTED */ @retval CUBEB_ERROR_NOT_SUPPORTED */
CUBEB_EXPORT int cubeb_stream_register_device_changed_callback(cubeb_stream * stream, CUBEB_EXPORT int
cubeb_device_changed_callback device_changed_callback); cubeb_stream_register_device_changed_callback(
cubeb_stream * stream,
cubeb_device_changed_callback device_changed_callback);
/** Return the user data pointer registered with the stream with cubeb_stream_init. /** Return the user data pointer registered with the stream with
cubeb_stream_init.
@param stream the stream for which to retrieve user data pointer. @param stream the stream for which to retrieve user data pointer.
@retval user data pointer */ @retval user data pointer */
CUBEB_EXPORT void * cubeb_stream_user_ptr(cubeb_stream * stream); CUBEB_EXPORT void *
cubeb_stream_user_ptr(cubeb_stream * stream);
/** Returns enumerated devices. /** Returns enumerated devices.
@param context @param context
@param devtype device type to include @param devtype device type to include
@param collection output collection. Must be destroyed with cubeb_device_collection_destroy @param collection output collection. Must be destroyed with
cubeb_device_collection_destroy
@retval CUBEB_OK in case of success @retval CUBEB_OK in case of success
@retval CUBEB_ERROR_INVALID_PARAMETER if collection is an invalid pointer @retval CUBEB_ERROR_INVALID_PARAMETER if collection is an invalid pointer
@retval CUBEB_ERROR_NOT_SUPPORTED */ @retval CUBEB_ERROR_NOT_SUPPORTED */
CUBEB_EXPORT int cubeb_enumerate_devices(cubeb * context, CUBEB_EXPORT int
cubeb_device_type devtype, cubeb_enumerate_devices(cubeb * context, cubeb_device_type devtype,
cubeb_device_collection * collection); cubeb_device_collection * collection);
/** Destroy a cubeb_device_collection, and its `cubeb_device_info`. /** Destroy a cubeb_device_collection, and its `cubeb_device_info`.
@param context @param context
@param collection collection to destroy @param collection collection to destroy
@retval CUBEB_OK @retval CUBEB_OK
@retval CUBEB_ERROR_INVALID_PARAMETER if collection is an invalid pointer */ @retval CUBEB_ERROR_INVALID_PARAMETER if collection is an invalid pointer */
CUBEB_EXPORT int cubeb_device_collection_destroy(cubeb * context, CUBEB_EXPORT int
cubeb_device_collection * collection); cubeb_device_collection_destroy(cubeb * context,
cubeb_device_collection * collection);
/** Registers a callback which is called when the system detects /** Registers a callback which is called when the system detects
a new device or a device is removed. a new device or a device is removed.
@ -662,17 +693,18 @@ CUBEB_EXPORT int cubeb_device_collection_destroy(cubeb * context,
@param devtype device type to include. Different callbacks and user pointers @param devtype device type to include. Different callbacks and user pointers
can be registered for each devtype. The hybrid devtype can be registered for each devtype. The hybrid devtype
`CUBEB_DEVICE_TYPE_INPUT | CUBEB_DEVICE_TYPE_OUTPUT` is also valid `CUBEB_DEVICE_TYPE_INPUT | CUBEB_DEVICE_TYPE_OUTPUT` is also valid
and will register the provided callback and user pointer in both sides. and will register the provided callback and user pointer in both
sides.
@param callback a function called whenever the system device list changes. @param callback a function called whenever the system device list changes.
Passing NULL allow to unregister a function. You have to unregister Passing NULL allow to unregister a function. You have to unregister
first before you register a new callback. first before you register a new callback.
@param user_ptr pointer to user specified data which will be present in @param user_ptr pointer to user specified data which will be present in
subsequent callbacks. subsequent callbacks.
@retval CUBEB_ERROR_NOT_SUPPORTED */ @retval CUBEB_ERROR_NOT_SUPPORTED */
CUBEB_EXPORT int cubeb_register_device_collection_changed(cubeb * context, CUBEB_EXPORT int
cubeb_device_type devtype, cubeb_register_device_collection_changed(
cubeb_device_collection_changed_callback callback, cubeb * context, cubeb_device_type devtype,
void * user_ptr); cubeb_device_collection_changed_callback callback, void * user_ptr);
/** Set a callback to be called with a message. /** Set a callback to be called with a message.
@param log_level CUBEB_LOG_VERBOSE, CUBEB_LOG_NORMAL. @param log_level CUBEB_LOG_VERBOSE, CUBEB_LOG_NORMAL.
@ -682,8 +714,9 @@ CUBEB_EXPORT int cubeb_register_device_collection_changed(cubeb * context,
@retval CUBEB_ERROR_INVALID_PARAMETER if either context or log_callback are @retval CUBEB_ERROR_INVALID_PARAMETER if either context or log_callback are
invalid pointers, or if level is not invalid pointers, or if level is not
in cubeb_log_level. */ in cubeb_log_level. */
CUBEB_EXPORT int cubeb_set_log_callback(cubeb_log_level log_level, CUBEB_EXPORT int
cubeb_log_callback log_callback); cubeb_set_log_callback(cubeb_log_level log_level,
cubeb_log_callback log_callback);
#if defined(__cplusplus) #if defined(__cplusplus)
} }

View file

@ -22,23 +22,25 @@
*/ */
/* /*
* From https://android.googlesource.com/platform/frameworks/base/+/android-2.2.3_r2.1/include/utils/Errors.h * From
* https://android.googlesource.com/platform/frameworks/base/+/android-2.2.3_r2.1/include/utils/Errors.h
*/ */
typedef int32_t status_t; typedef int32_t status_t;
/* /*
* From https://android.googlesource.com/platform/frameworks/base/+/android-2.2.3_r2.1/include/media/AudioTrack.h * From
* https://android.googlesource.com/platform/frameworks/base/+/android-2.2.3_r2.1/include/media/AudioTrack.h
*/ */
struct Buffer { struct Buffer {
uint32_t flags; uint32_t flags;
int channelCount; int channelCount;
int format; int format;
size_t frameCount; size_t frameCount;
size_t size; size_t size;
union { union {
void* raw; void * raw;
short* i16; short * i16;
int8_t* i8; int8_t * i8;
}; };
}; };
@ -52,7 +54,8 @@ enum event_type {
}; };
/** /**
* From https://android.googlesource.com/platform/frameworks/base/+/android-2.2.3_r2.1/include/media/AudioSystem.h * From
* https://android.googlesource.com/platform/frameworks/base/+/android-2.2.3_r2.1/include/media/AudioSystem.h
* and * and
* https://android.googlesource.com/platform/system/core/+/android-4.2.2_r1/include/system/audio.h * https://android.googlesource.com/platform/system/core/+/android-4.2.2_r1/include/system/audio.h
*/ */
@ -60,17 +63,19 @@ enum event_type {
#define AUDIO_STREAM_TYPE_MUSIC 3 #define AUDIO_STREAM_TYPE_MUSIC 3
enum { enum {
AUDIO_CHANNEL_OUT_FRONT_LEFT_ICS = 0x1, AUDIO_CHANNEL_OUT_FRONT_LEFT_ICS = 0x1,
AUDIO_CHANNEL_OUT_FRONT_RIGHT_ICS = 0x2, AUDIO_CHANNEL_OUT_FRONT_RIGHT_ICS = 0x2,
AUDIO_CHANNEL_OUT_MONO_ICS = AUDIO_CHANNEL_OUT_FRONT_LEFT_ICS, AUDIO_CHANNEL_OUT_MONO_ICS = AUDIO_CHANNEL_OUT_FRONT_LEFT_ICS,
AUDIO_CHANNEL_OUT_STEREO_ICS = (AUDIO_CHANNEL_OUT_FRONT_LEFT_ICS | AUDIO_CHANNEL_OUT_FRONT_RIGHT_ICS) AUDIO_CHANNEL_OUT_STEREO_ICS =
(AUDIO_CHANNEL_OUT_FRONT_LEFT_ICS | AUDIO_CHANNEL_OUT_FRONT_RIGHT_ICS)
} AudioTrack_ChannelMapping_ICS; } AudioTrack_ChannelMapping_ICS;
enum { enum {
AUDIO_CHANNEL_OUT_FRONT_LEFT_Legacy = 0x4, AUDIO_CHANNEL_OUT_FRONT_LEFT_Legacy = 0x4,
AUDIO_CHANNEL_OUT_FRONT_RIGHT_Legacy = 0x8, AUDIO_CHANNEL_OUT_FRONT_RIGHT_Legacy = 0x8,
AUDIO_CHANNEL_OUT_MONO_Legacy = AUDIO_CHANNEL_OUT_FRONT_LEFT_Legacy, AUDIO_CHANNEL_OUT_MONO_Legacy = AUDIO_CHANNEL_OUT_FRONT_LEFT_Legacy,
AUDIO_CHANNEL_OUT_STEREO_Legacy = (AUDIO_CHANNEL_OUT_FRONT_LEFT_Legacy | AUDIO_CHANNEL_OUT_FRONT_RIGHT_Legacy) AUDIO_CHANNEL_OUT_STEREO_Legacy = (AUDIO_CHANNEL_OUT_FRONT_LEFT_Legacy |
AUDIO_CHANNEL_OUT_FRONT_RIGHT_Legacy)
} AudioTrack_ChannelMapping_Legacy; } AudioTrack_ChannelMapping_Legacy;
typedef enum { typedef enum {
@ -78,4 +83,3 @@ typedef enum {
AUDIO_FORMAT_PCM_SUB_16_BIT = 0x1, AUDIO_FORMAT_PCM_SUB_16_BIT = 0x1,
AUDIO_FORMAT_PCM_16_BIT = (AUDIO_FORMAT_PCM | AUDIO_FORMAT_PCM_SUB_16_BIT), AUDIO_FORMAT_PCM_16_BIT = (AUDIO_FORMAT_PCM | AUDIO_FORMAT_PCM_SUB_16_BIT),
} AudioTrack_SampleType; } AudioTrack_SampleType;

View file

@ -1,9 +1,9 @@
#ifndef _CUBEB_OUTPUT_LATENCY_H_ #ifndef _CUBEB_OUTPUT_LATENCY_H_
#define _CUBEB_OUTPUT_LATENCY_H_ #define _CUBEB_OUTPUT_LATENCY_H_
#include <stdbool.h>
#include "cubeb_media_library.h"
#include "../cubeb-jni.h" #include "../cubeb-jni.h"
#include "cubeb_media_library.h"
#include <stdbool.h>
struct output_latency_function { struct output_latency_function {
media_lib * from_lib; media_lib * from_lib;
@ -23,7 +23,7 @@ cubeb_output_latency_load_method(int version)
ol->version = version; ol->version = version;
if (ol->version > ANDROID_JELLY_BEAN_MR1_4_2){ if (ol->version > ANDROID_JELLY_BEAN_MR1_4_2) {
ol->from_jni = cubeb_jni_init(); ol->from_jni = cubeb_jni_init();
return ol; return ol;
} }
@ -36,7 +36,7 @@ bool
cubeb_output_latency_method_is_loaded(output_latency_function * ol) cubeb_output_latency_method_is_loaded(output_latency_function * ol)
{ {
assert(ol); assert(ol);
if (ol->version > ANDROID_JELLY_BEAN_MR1_4_2){ if (ol->version > ANDROID_JELLY_BEAN_MR1_4_2) {
return !!ol->from_jni; return !!ol->from_jni;
} }
@ -66,7 +66,7 @@ cubeb_get_output_latency(output_latency_function * ol)
{ {
assert(cubeb_output_latency_method_is_loaded(ol)); assert(cubeb_output_latency_method_is_loaded(ol));
if (ol->version > ANDROID_JELLY_BEAN_MR1_4_2){ if (ol->version > ANDROID_JELLY_BEAN_MR1_4_2) {
return cubeb_get_output_latency_from_jni(ol->from_jni); return cubeb_get_output_latency_from_jni(ol->from_jni);
} }

View file

@ -3,7 +3,7 @@
struct media_lib { struct media_lib {
void * libmedia; void * libmedia;
int32_t (* get_output_latency)(uint32_t * latency, int stream_type); int32_t (*get_output_latency)(uint32_t * latency, int stream_type);
}; };
typedef struct media_lib media_lib; typedef struct media_lib media_lib;
@ -17,15 +17,17 @@ cubeb_load_media_library()
return NULL; return NULL;
} }
// Get the latency, in ms, from AudioFlinger. First, try the most recent signature. // Get the latency, in ms, from AudioFlinger. First, try the most recent
// status_t AudioSystem::getOutputLatency(uint32_t* latency, audio_stream_type_t streamType) // signature. status_t AudioSystem::getOutputLatency(uint32_t* latency,
ml.get_output_latency = // audio_stream_type_t streamType)
dlsym(ml.libmedia, "_ZN7android11AudioSystem16getOutputLatencyEPj19audio_stream_type_t"); ml.get_output_latency = dlsym(
ml.libmedia,
"_ZN7android11AudioSystem16getOutputLatencyEPj19audio_stream_type_t");
if (!ml.get_output_latency) { if (!ml.get_output_latency) {
// In case of failure, try the signature from legacy version. // In case of failure, try the signature from legacy version.
// status_t AudioSystem::getOutputLatency(uint32_t* latency, int streamType) // status_t AudioSystem::getOutputLatency(uint32_t* latency, int streamType)
ml.get_output_latency = ml.get_output_latency =
dlsym(ml.libmedia, "_ZN7android11AudioSystem16getOutputLatencyEPji"); dlsym(ml.libmedia, "_ZN7android11AudioSystem16getOutputLatencyEPji");
if (!ml.get_output_latency) { if (!ml.get_output_latency) {
return NULL; return NULL;
} }

View file

@ -29,23 +29,23 @@
/** Audio recording preset */ /** Audio recording preset */
/** Audio recording preset key */ /** Audio recording preset key */
#define SL_ANDROID_KEY_RECORDING_PRESET ((const SLchar*) "androidRecordingPreset") #define SL_ANDROID_KEY_RECORDING_PRESET \
((const SLchar *)"androidRecordingPreset")
/** Audio recording preset values */ /** Audio recording preset values */
/** preset "none" cannot be set, it is used to indicate the current settings /** preset "none" cannot be set, it is used to indicate the current settings
* do not match any of the presets. */ * do not match any of the presets. */
#define SL_ANDROID_RECORDING_PRESET_NONE ((SLuint32) 0x00000000) #define SL_ANDROID_RECORDING_PRESET_NONE ((SLuint32)0x00000000)
/** generic recording configuration on the platform */ /** generic recording configuration on the platform */
#define SL_ANDROID_RECORDING_PRESET_GENERIC ((SLuint32) 0x00000001) #define SL_ANDROID_RECORDING_PRESET_GENERIC ((SLuint32)0x00000001)
/** uses the microphone audio source with the same orientation as the camera /** uses the microphone audio source with the same orientation as the camera
* if available, the main device microphone otherwise */ * if available, the main device microphone otherwise */
#define SL_ANDROID_RECORDING_PRESET_CAMCORDER ((SLuint32) 0x00000002) #define SL_ANDROID_RECORDING_PRESET_CAMCORDER ((SLuint32)0x00000002)
/** uses the main microphone tuned for voice recognition */ /** uses the main microphone tuned for voice recognition */
#define SL_ANDROID_RECORDING_PRESET_VOICE_RECOGNITION ((SLuint32) 0x00000003) #define SL_ANDROID_RECORDING_PRESET_VOICE_RECOGNITION ((SLuint32)0x00000003)
/** uses the main microphone tuned for audio communications */ /** uses the main microphone tuned for audio communications */
#define SL_ANDROID_RECORDING_PRESET_VOICE_COMMUNICATION ((SLuint32) 0x00000004) #define SL_ANDROID_RECORDING_PRESET_VOICE_COMMUNICATION ((SLuint32)0x00000004)
/** uses the main microphone unprocessed */ /** uses the main microphone unprocessed */
#define SL_ANDROID_RECORDING_PRESET_UNPROCESSED ((SLuint32) 0x00000005) #define SL_ANDROID_RECORDING_PRESET_UNPROCESSED ((SLuint32)0x00000005)
/*---------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------*/
/* Android AudioPlayer configuration */ /* Android AudioPlayer configuration */
@ -53,22 +53,21 @@
/** Audio playback stream type */ /** Audio playback stream type */
/** Audio playback stream type key */ /** Audio playback stream type key */
#define SL_ANDROID_KEY_STREAM_TYPE ((const SLchar*) "androidPlaybackStreamType") #define SL_ANDROID_KEY_STREAM_TYPE ((const SLchar *)"androidPlaybackStreamType")
/** Audio playback stream type values */ /** Audio playback stream type values */
/* same as android.media.AudioManager.STREAM_VOICE_CALL */ /* same as android.media.AudioManager.STREAM_VOICE_CALL */
#define SL_ANDROID_STREAM_VOICE ((SLint32) 0x00000000) #define SL_ANDROID_STREAM_VOICE ((SLint32)0x00000000)
/* same as android.media.AudioManager.STREAM_SYSTEM */ /* same as android.media.AudioManager.STREAM_SYSTEM */
#define SL_ANDROID_STREAM_SYSTEM ((SLint32) 0x00000001) #define SL_ANDROID_STREAM_SYSTEM ((SLint32)0x00000001)
/* same as android.media.AudioManager.STREAM_RING */ /* same as android.media.AudioManager.STREAM_RING */
#define SL_ANDROID_STREAM_RING ((SLint32) 0x00000002) #define SL_ANDROID_STREAM_RING ((SLint32)0x00000002)
/* same as android.media.AudioManager.STREAM_MUSIC */ /* same as android.media.AudioManager.STREAM_MUSIC */
#define SL_ANDROID_STREAM_MEDIA ((SLint32) 0x00000003) #define SL_ANDROID_STREAM_MEDIA ((SLint32)0x00000003)
/* same as android.media.AudioManager.STREAM_ALARM */ /* same as android.media.AudioManager.STREAM_ALARM */
#define SL_ANDROID_STREAM_ALARM ((SLint32) 0x00000004) #define SL_ANDROID_STREAM_ALARM ((SLint32)0x00000004)
/* same as android.media.AudioManager.STREAM_NOTIFICATION */ /* same as android.media.AudioManager.STREAM_NOTIFICATION */
#define SL_ANDROID_STREAM_NOTIFICATION ((SLint32) 0x00000005) #define SL_ANDROID_STREAM_NOTIFICATION ((SLint32)0x00000005)
/*---------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------*/
/* Android AudioPlayer and AudioRecorder configuration */ /* Android AudioPlayer and AudioRecorder configuration */
@ -85,18 +84,21 @@
* granted or not. * granted or not.
*/ */
/** Audio Performance mode key */ /** Audio Performance mode key */
#define SL_ANDROID_KEY_PERFORMANCE_MODE ((const SLchar*) "androidPerformanceMode") #define SL_ANDROID_KEY_PERFORMANCE_MODE \
((const SLchar *)"androidPerformanceMode")
/** Audio performance values */ /** Audio performance values */
/* No specific performance requirement. Allows HW and SW pre/post processing. */ /* No specific performance requirement. Allows HW and SW pre/post
#define SL_ANDROID_PERFORMANCE_NONE ((SLuint32) 0x00000000) * processing. */
#define SL_ANDROID_PERFORMANCE_NONE ((SLuint32)0x00000000)
/* Priority given to latency. No HW or software pre/post processing. /* Priority given to latency. No HW or software pre/post processing.
* This is the default if no performance mode is specified. */ * This is the default if no performance mode is specified. */
#define SL_ANDROID_PERFORMANCE_LATENCY ((SLuint32) 0x00000001) #define SL_ANDROID_PERFORMANCE_LATENCY ((SLuint32)0x00000001)
/* Priority given to latency while still allowing HW pre and post processing. */ /* Priority given to latency while still allowing HW pre and post
#define SL_ANDROID_PERFORMANCE_LATENCY_EFFECTS ((SLuint32) 0x00000002) * processing. */
#define SL_ANDROID_PERFORMANCE_LATENCY_EFFECTS ((SLuint32)0x00000002)
/* Priority given to power saving if latency is not a concern. /* Priority given to power saving if latency is not a concern.
* Allows HW and SW pre/post processing. */ * Allows HW and SW pre/post processing. */
#define SL_ANDROID_PERFORMANCE_POWER_SAVING ((SLuint32) 0x00000003) #define SL_ANDROID_PERFORMANCE_POWER_SAVING ((SLuint32)0x00000003)
#endif /* OPENSL_ES_ANDROIDCONFIGURATION_H_ */ #endif /* OPENSL_ES_ANDROIDCONFIGURATION_H_ */

View file

@ -8,8 +8,8 @@
#define CUBEB_INTERNAL_0eb56756_4e20_4404_a76d_42bf88cd15a5 #define CUBEB_INTERNAL_0eb56756_4e20_4404_a76d_42bf88cd15a5
#include "cubeb/cubeb.h" #include "cubeb/cubeb.h"
#include "cubeb_log.h"
#include "cubeb_assert.h" #include "cubeb_assert.h"
#include "cubeb_log.h"
#include <stdio.h> #include <stdio.h>
#include <string.h> #include <string.h>
@ -21,7 +21,7 @@
#define CLANG_ANALYZER_NORETURN #define CLANG_ANALYZER_NORETURN
#endif // ifndef CLANG_ANALYZER_NORETURN #endif // ifndef CLANG_ANALYZER_NORETURN
#endif // __has_feature(attribute_analyzer_noreturn) #endif // __has_feature(attribute_analyzer_noreturn)
#else // __clang__ #else // __clang__
#define CLANG_ANALYZER_NORETURN #define CLANG_ANALYZER_NORETURN
#endif #endif
@ -34,48 +34,41 @@ extern "C" {
#endif #endif
struct cubeb_ops { struct cubeb_ops {
int (* init)(cubeb ** context, char const * context_name); int (*init)(cubeb ** context, char const * context_name);
char const * (* get_backend_id)(cubeb * context); char const * (*get_backend_id)(cubeb * context);
int (* get_max_channel_count)(cubeb * context, uint32_t * max_channels); int (*get_max_channel_count)(cubeb * context, uint32_t * max_channels);
int (* get_min_latency)(cubeb * context, int (*get_min_latency)(cubeb * context, cubeb_stream_params params,
cubeb_stream_params params, uint32_t * latency_ms);
uint32_t * latency_ms); int (*get_preferred_sample_rate)(cubeb * context, uint32_t * rate);
int (* get_preferred_sample_rate)(cubeb * context, uint32_t * rate); int (*enumerate_devices)(cubeb * context, cubeb_device_type type,
int (* enumerate_devices)(cubeb * context, cubeb_device_type type, cubeb_device_collection * collection);
cubeb_device_collection * collection); int (*device_collection_destroy)(cubeb * context,
int (* device_collection_destroy)(cubeb * context, cubeb_device_collection * collection);
cubeb_device_collection * collection); void (*destroy)(cubeb * context);
void (* destroy)(cubeb * context); int (*stream_init)(cubeb * context, cubeb_stream ** stream,
int (* stream_init)(cubeb * context, char const * stream_name, cubeb_devid input_device,
cubeb_stream ** stream, cubeb_stream_params * input_stream_params,
char const * stream_name, cubeb_devid output_device,
cubeb_devid input_device, cubeb_stream_params * output_stream_params,
cubeb_stream_params * input_stream_params, unsigned int latency, cubeb_data_callback data_callback,
cubeb_devid output_device, cubeb_state_callback state_callback, void * user_ptr);
cubeb_stream_params * output_stream_params, void (*stream_destroy)(cubeb_stream * stream);
unsigned int latency, int (*stream_start)(cubeb_stream * stream);
cubeb_data_callback data_callback, int (*stream_stop)(cubeb_stream * stream);
cubeb_state_callback state_callback, int (*stream_get_position)(cubeb_stream * stream, uint64_t * position);
void * user_ptr); int (*stream_get_latency)(cubeb_stream * stream, uint32_t * latency);
void (* stream_destroy)(cubeb_stream * stream); int (*stream_get_input_latency)(cubeb_stream * stream, uint32_t * latency);
int (* stream_start)(cubeb_stream * stream); int (*stream_set_volume)(cubeb_stream * stream, float volumes);
int (* stream_stop)(cubeb_stream * stream); int (*stream_set_name)(cubeb_stream * stream, char const * stream_name);
int (* stream_reset_default_device)(cubeb_stream * stream); int (*stream_get_current_device)(cubeb_stream * stream,
int (* stream_get_position)(cubeb_stream * stream, uint64_t * position); cubeb_device ** const device);
int (* stream_get_latency)(cubeb_stream * stream, uint32_t * latency); int (*stream_device_destroy)(cubeb_stream * stream, cubeb_device * device);
int (* stream_get_input_latency)(cubeb_stream * stream, uint32_t * latency); int (*stream_register_device_changed_callback)(
int (* stream_set_volume)(cubeb_stream * stream, float volumes); cubeb_stream * stream,
int (* stream_set_name)(cubeb_stream * stream, char const * stream_name); cubeb_device_changed_callback device_changed_callback);
int (* stream_get_current_device)(cubeb_stream * stream, int (*register_device_collection_changed)(
cubeb_device ** const device); cubeb * context, cubeb_device_type devtype,
int (* stream_device_destroy)(cubeb_stream * stream, cubeb_device_collection_changed_callback callback, void * user_ptr);
cubeb_device * device);
int (* stream_register_device_changed_callback)(cubeb_stream * stream,
cubeb_device_changed_callback device_changed_callback);
int (* register_device_collection_changed)(cubeb * context,
cubeb_device_type devtype,
cubeb_device_collection_changed_callback callback,
void * user_ptr);
}; };
#endif /* CUBEB_INTERNAL_0eb56756_4e20_4404_a76d_42bf88cd15a5 */ #endif /* CUBEB_INTERNAL_0eb56756_4e20_4404_a76d_42bf88cd15a5 */

View file

@ -1,6 +1,8 @@
/* clang-format off */
#include "jni.h" #include "jni.h"
#include <assert.h> #include <assert.h>
#include "cubeb-jni-instances.h" #include "cubeb-jni-instances.h"
/* clang-format on */
#define AUDIO_STREAM_TYPE_MUSIC 3 #define AUDIO_STREAM_TYPE_MUSIC 3
@ -10,8 +12,7 @@ struct cubeb_jni {
jmethodID s_get_output_latency_id = nullptr; jmethodID s_get_output_latency_id = nullptr;
}; };
extern "C" extern "C" cubeb_jni *
cubeb_jni *
cubeb_jni_init() cubeb_jni_init()
{ {
jobject ctx_obj = cubeb_jni_get_context_instance(); jobject ctx_obj = cubeb_jni_get_context_instance();
@ -23,18 +24,28 @@ cubeb_jni_init()
cubeb_jni * cubeb_jni_ptr = new cubeb_jni; cubeb_jni * cubeb_jni_ptr = new cubeb_jni;
assert(cubeb_jni_ptr); assert(cubeb_jni_ptr);
// Find the audio manager object and make it global to call it from another method // Find the audio manager object and make it global to call it from another
// method
jclass context_class = jni_env->FindClass("android/content/Context"); jclass context_class = jni_env->FindClass("android/content/Context");
jfieldID audio_service_field = jni_env->GetStaticFieldID(context_class, "AUDIO_SERVICE", "Ljava/lang/String;"); jfieldID audio_service_field = jni_env->GetStaticFieldID(
jstring jstr = (jstring)jni_env->GetStaticObjectField(context_class, audio_service_field); context_class, "AUDIO_SERVICE", "Ljava/lang/String;");
jmethodID get_system_service_id = jni_env->GetMethodID(context_class, "getSystemService", "(Ljava/lang/String;)Ljava/lang/Object;"); jstring jstr = (jstring)jni_env->GetStaticObjectField(context_class,
jobject audio_manager_obj = jni_env->CallObjectMethod(ctx_obj, get_system_service_id, jstr); audio_service_field);
cubeb_jni_ptr->s_audio_manager_obj = reinterpret_cast<jobject>(jni_env->NewGlobalRef(audio_manager_obj)); jmethodID get_system_service_id =
jni_env->GetMethodID(context_class, "getSystemService",
"(Ljava/lang/String;)Ljava/lang/Object;");
jobject audio_manager_obj =
jni_env->CallObjectMethod(ctx_obj, get_system_service_id, jstr);
cubeb_jni_ptr->s_audio_manager_obj =
reinterpret_cast<jobject>(jni_env->NewGlobalRef(audio_manager_obj));
// Make the audio manager class a global reference in order to preserve method id // Make the audio manager class a global reference in order to preserve method
// id
jclass audio_manager_class = jni_env->FindClass("android/media/AudioManager"); jclass audio_manager_class = jni_env->FindClass("android/media/AudioManager");
cubeb_jni_ptr->s_audio_manager_class = reinterpret_cast<jclass>(jni_env->NewGlobalRef(audio_manager_class)); cubeb_jni_ptr->s_audio_manager_class =
cubeb_jni_ptr->s_get_output_latency_id = jni_env->GetMethodID (audio_manager_class, "getOutputLatency", "(I)I"); reinterpret_cast<jclass>(jni_env->NewGlobalRef(audio_manager_class));
cubeb_jni_ptr->s_get_output_latency_id =
jni_env->GetMethodID(audio_manager_class, "getOutputLatency", "(I)I");
jni_env->DeleteLocalRef(ctx_obj); jni_env->DeleteLocalRef(ctx_obj);
jni_env->DeleteLocalRef(context_class); jni_env->DeleteLocalRef(context_class);
@ -45,16 +56,19 @@ cubeb_jni_init()
return cubeb_jni_ptr; return cubeb_jni_ptr;
} }
extern "C" extern "C" int
int cubeb_get_output_latency_from_jni(cubeb_jni * cubeb_jni_ptr) cubeb_get_output_latency_from_jni(cubeb_jni * cubeb_jni_ptr)
{ {
assert(cubeb_jni_ptr); assert(cubeb_jni_ptr);
JNIEnv * jni_env = cubeb_get_jni_env_for_thread(); JNIEnv * jni_env = cubeb_get_jni_env_for_thread();
return jni_env->CallIntMethod(cubeb_jni_ptr->s_audio_manager_obj, cubeb_jni_ptr->s_get_output_latency_id, AUDIO_STREAM_TYPE_MUSIC); //param: AudioManager.STREAM_MUSIC return jni_env->CallIntMethod(
cubeb_jni_ptr->s_audio_manager_obj,
cubeb_jni_ptr->s_get_output_latency_id,
AUDIO_STREAM_TYPE_MUSIC); // param: AudioManager.STREAM_MUSIC
} }
extern "C" extern "C" void
void cubeb_jni_destroy(cubeb_jni * cubeb_jni_ptr) cubeb_jni_destroy(cubeb_jni * cubeb_jni_ptr)
{ {
assert(cubeb_jni_ptr); assert(cubeb_jni_ptr);

View file

@ -3,8 +3,11 @@
typedef struct cubeb_jni cubeb_jni; typedef struct cubeb_jni cubeb_jni;
cubeb_jni * cubeb_jni_init(); cubeb_jni *
int cubeb_get_output_latency_from_jni(cubeb_jni * cubeb_jni_ptr); cubeb_jni_init();
void cubeb_jni_destroy(cubeb_jni * cubeb_jni_ptr); int
cubeb_get_output_latency_from_jni(cubeb_jni * cubeb_jni_ptr);
void
cubeb_jni_destroy(cubeb_jni * cubeb_jni_ptr);
#endif // _CUBEB_JNI_H_ #endif // _CUBEB_JNI_H_

View file

@ -10,27 +10,22 @@
#include <SLES/OpenSLES.h> #include <SLES/OpenSLES.h>
static SLresult static SLresult
cubeb_get_sles_engine(SLObjectItf * pEngine, cubeb_get_sles_engine(SLObjectItf * pEngine, SLuint32 numOptions,
SLuint32 numOptions,
const SLEngineOption * pEngineOptions, const SLEngineOption * pEngineOptions,
SLuint32 numInterfaces, SLuint32 numInterfaces,
const SLInterfaceID * pInterfaceIds, const SLInterfaceID * pInterfaceIds,
const SLboolean * pInterfaceRequired) const SLboolean * pInterfaceRequired)
{ {
return slCreateEngine(pEngine, return slCreateEngine(pEngine, numOptions, pEngineOptions, numInterfaces,
numOptions, pInterfaceIds, pInterfaceRequired);
pEngineOptions,
numInterfaces,
pInterfaceIds,
pInterfaceRequired);
} }
static void static void
cubeb_destroy_sles_engine(SLObjectItf * self) cubeb_destroy_sles_engine(SLObjectItf * self)
{ {
if (*self != NULL) { if (*self != NULL) {
(**self)->Destroy(*self); (**self)->Destroy(*self);
*self = NULL; *self = NULL;
} }
} }

View file

@ -5,14 +5,14 @@
* accompanying file LICENSE for details. * accompanying file LICENSE for details.
*/ */
#undef NDEBUG #undef NDEBUG
#include "cubeb/cubeb.h"
#include "cubeb-internal.h"
#include <assert.h> #include <assert.h>
#include <stddef.h> #include <stddef.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include "cubeb/cubeb.h"
#include "cubeb-internal.h"
#define NELEMS(x) ((int) (sizeof(x) / sizeof(x[0]))) #define NELEMS(x) ((int)(sizeof(x) / sizeof(x[0])))
struct cubeb { struct cubeb {
struct cubeb_ops * ops; struct cubeb_ops * ops;
@ -28,46 +28,64 @@ struct cubeb_stream {
}; };
#if defined(USE_PULSE) #if defined(USE_PULSE)
int pulse_init(cubeb ** context, char const * context_name); int
pulse_init(cubeb ** context, char const * context_name);
#endif #endif
#if defined(USE_PULSE_RUST) #if defined(USE_PULSE_RUST)
int pulse_rust_init(cubeb ** contet, char const * context_name); int
pulse_rust_init(cubeb ** contet, char const * context_name);
#endif #endif
#if defined(USE_JACK) #if defined(USE_JACK)
int jack_init (cubeb ** context, char const * context_name); int
jack_init(cubeb ** context, char const * context_name);
#endif #endif
#if defined(USE_ALSA) #if defined(USE_ALSA)
int alsa_init(cubeb ** context, char const * context_name); int
alsa_init(cubeb ** context, char const * context_name);
#endif #endif
#if defined(USE_AUDIOUNIT) #if defined(USE_AUDIOUNIT)
int audiounit_init(cubeb ** context, char const * context_name); int
audiounit_init(cubeb ** context, char const * context_name);
#endif #endif
#if defined(USE_AUDIOUNIT_RUST) #if defined(USE_AUDIOUNIT_RUST)
int audiounit_rust_init(cubeb ** contet, char const * context_name); int
audiounit_rust_init(cubeb ** contet, char const * context_name);
#endif #endif
#if defined(USE_WINMM) #if defined(USE_WINMM)
int winmm_init(cubeb ** context, char const * context_name); int
winmm_init(cubeb ** context, char const * context_name);
#endif #endif
#if defined(USE_WASAPI) #if defined(USE_WASAPI)
int wasapi_init(cubeb ** context, char const * context_name); int
wasapi_init(cubeb ** context, char const * context_name);
#endif #endif
#if defined(USE_SNDIO) #if defined(USE_SNDIO)
int sndio_init(cubeb ** context, char const * context_name); int
sndio_init(cubeb ** context, char const * context_name);
#endif #endif
#if defined(USE_SUN) #if defined(USE_SUN)
int sun_init(cubeb ** context, char const * context_name); int
sun_init(cubeb ** context, char const * context_name);
#endif #endif
#if defined(USE_OPENSL) #if defined(USE_OPENSL)
int opensl_init(cubeb ** context, char const * context_name); int
opensl_init(cubeb ** context, char const * context_name);
#endif #endif
#if defined(USE_OSS) #if defined(USE_OSS)
int oss_init(cubeb ** context, char const * context_name); int
oss_init(cubeb ** context, char const * context_name);
#endif
#if defined(USE_AAUDIO)
int
aaudio_init(cubeb ** context, char const * context_name);
#endif #endif
#if defined(USE_AUDIOTRACK) #if defined(USE_AUDIOTRACK)
int audiotrack_init(cubeb ** context, char const * context_name); int
audiotrack_init(cubeb ** context, char const * context_name);
#endif #endif
#if defined(USE_KAI) #if defined(USE_KAI)
int kai_init(cubeb ** context, char const * context_name); int
kai_init(cubeb ** context, char const * context_name);
#endif #endif
static int static int
@ -76,28 +94,32 @@ validate_stream_params(cubeb_stream_params * input_stream_params,
{ {
XASSERT(input_stream_params || output_stream_params); XASSERT(input_stream_params || output_stream_params);
if (output_stream_params) { if (output_stream_params) {
if (output_stream_params->rate < 1000 || output_stream_params->rate > 192000 || if (output_stream_params->rate < 1000 ||
output_stream_params->channels < 1 || output_stream_params->channels > UINT8_MAX) { output_stream_params->rate > 192000 ||
output_stream_params->channels < 1 ||
output_stream_params->channels > UINT8_MAX) {
return CUBEB_ERROR_INVALID_FORMAT; return CUBEB_ERROR_INVALID_FORMAT;
} }
} }
if (input_stream_params) { if (input_stream_params) {
if (input_stream_params->rate < 1000 || input_stream_params->rate > 192000 || if (input_stream_params->rate < 1000 ||
input_stream_params->channels < 1 || input_stream_params->channels > UINT8_MAX) { input_stream_params->rate > 192000 ||
input_stream_params->channels < 1 ||
input_stream_params->channels > UINT8_MAX) {
return CUBEB_ERROR_INVALID_FORMAT; return CUBEB_ERROR_INVALID_FORMAT;
} }
} }
// Rate and sample format must be the same for input and output, if using a // Rate and sample format must be the same for input and output, if using a
// duplex stream // duplex stream
if (input_stream_params && output_stream_params) { if (input_stream_params && output_stream_params) {
if (input_stream_params->rate != output_stream_params->rate || if (input_stream_params->rate != output_stream_params->rate ||
input_stream_params->format != output_stream_params->format) { input_stream_params->format != output_stream_params->format) {
return CUBEB_ERROR_INVALID_FORMAT; return CUBEB_ERROR_INVALID_FORMAT;
} }
} }
cubeb_stream_params * params = input_stream_params ? cubeb_stream_params * params =
input_stream_params : output_stream_params; input_stream_params ? input_stream_params : output_stream_params;
switch (params->format) { switch (params->format) {
case CUBEB_SAMPLE_S16LE: case CUBEB_SAMPLE_S16LE:
@ -120,9 +142,10 @@ validate_latency(int latency)
} }
int int
cubeb_init(cubeb ** context, char const * context_name, char const * backend_name) cubeb_init(cubeb ** context, char const * context_name,
char const * backend_name)
{ {
int (* init_oneshot)(cubeb **, char const *) = NULL; int (*init_oneshot)(cubeb **, char const *) = NULL;
if (backend_name != NULL) { if (backend_name != NULL) {
if (!strcmp(backend_name, "pulse")) { if (!strcmp(backend_name, "pulse")) {
@ -172,6 +195,10 @@ cubeb_init(cubeb ** context, char const * context_name, char const * backend_nam
} else if (!strcmp(backend_name, "oss")) { } else if (!strcmp(backend_name, "oss")) {
#if defined(USE_OSS) #if defined(USE_OSS)
init_oneshot = oss_init; init_oneshot = oss_init;
#endif
} else if (!strcmp(backend_name, "aaudio")) {
#if defined(USE_AAUDIO)
init_oneshot = aaudio_init;
#endif #endif
} else if (!strcmp(backend_name, "audiotrack")) { } else if (!strcmp(backend_name, "audiotrack")) {
#if defined(USE_AUDIOTRACK) #if defined(USE_AUDIOTRACK)
@ -186,7 +213,7 @@ cubeb_init(cubeb ** context, char const * context_name, char const * backend_nam
} }
} }
int (* default_init[])(cubeb **, char const *) = { int (*default_init[])(cubeb **, char const *) = {
/* /*
* init_oneshot must be at the top to allow user * init_oneshot must be at the top to allow user
* to override all other choices * to override all other choices
@ -207,7 +234,7 @@ cubeb_init(cubeb ** context, char const * context_name, char const * backend_nam
#if defined(USE_ALSA) #if defined(USE_ALSA)
alsa_init, alsa_init,
#endif #endif
#if defined (USE_OSS) #if defined(USE_OSS)
oss_init, oss_init,
#endif #endif
#if defined(USE_AUDIOUNIT_RUST) #if defined(USE_AUDIOUNIT_RUST)
@ -227,6 +254,11 @@ cubeb_init(cubeb ** context, char const * context_name, char const * backend_nam
#endif #endif
#if defined(USE_OPENSL) #if defined(USE_OPENSL)
opensl_init, opensl_init,
#endif
// TODO: should probably be preferred over OpenSLES when available.
// Initialization will fail on old android devices.
#if defined(USE_AAUDIO)
aaudio_init,
#endif #endif
#if defined(USE_AUDIOTRACK) #if defined(USE_AUDIOTRACK)
audiotrack_init, audiotrack_init,
@ -241,7 +273,7 @@ cubeb_init(cubeb ** context, char const * context_name, char const * backend_nam
return CUBEB_ERROR_INVALID_PARAMETER; return CUBEB_ERROR_INVALID_PARAMETER;
} }
#define OK(fn) assert((* context)->ops->fn) #define OK(fn) assert((*context)->ops->fn)
for (i = 0; i < NELEMS(default_init); ++i) { for (i = 0; i < NELEMS(default_init); ++i) {
if (default_init[i] && default_init[i](context, context_name) == CUBEB_OK) { if (default_init[i] && default_init[i](context, context_name) == CUBEB_OK) {
/* Assert that the minimal API is implemented. */ /* Assert that the minimal API is implemented. */
@ -283,7 +315,8 @@ cubeb_get_max_channel_count(cubeb * context, uint32_t * max_channels)
} }
int int
cubeb_get_min_latency(cubeb * context, cubeb_stream_params * params, uint32_t * latency_ms) cubeb_get_min_latency(cubeb * context, cubeb_stream_params * params,
uint32_t * latency_ms)
{ {
if (!context || !params || !latency_ms) { if (!context || !params || !latency_ms) {
return CUBEB_ERROR_INVALID_PARAMETER; return CUBEB_ERROR_INVALID_PARAMETER;
@ -321,15 +354,13 @@ cubeb_destroy(cubeb * context)
} }
int int
cubeb_stream_init(cubeb * context, cubeb_stream ** stream, char const * stream_name, cubeb_stream_init(cubeb * context, cubeb_stream ** stream,
cubeb_devid input_device, char const * stream_name, cubeb_devid input_device,
cubeb_stream_params * input_stream_params, cubeb_stream_params * input_stream_params,
cubeb_devid output_device, cubeb_devid output_device,
cubeb_stream_params * output_stream_params, cubeb_stream_params * output_stream_params,
unsigned int latency, unsigned int latency, cubeb_data_callback data_callback,
cubeb_data_callback data_callback, cubeb_state_callback state_callback, void * user_ptr)
cubeb_state_callback state_callback,
void * user_ptr)
{ {
int r; int r;
@ -337,24 +368,20 @@ cubeb_stream_init(cubeb * context, cubeb_stream ** stream, char const * stream_n
return CUBEB_ERROR_INVALID_PARAMETER; return CUBEB_ERROR_INVALID_PARAMETER;
} }
if ((r = validate_stream_params(input_stream_params, output_stream_params)) != CUBEB_OK || if ((r = validate_stream_params(input_stream_params, output_stream_params)) !=
CUBEB_OK ||
(r = validate_latency(latency)) != CUBEB_OK) { (r = validate_latency(latency)) != CUBEB_OK) {
return r; return r;
} }
r = context->ops->stream_init(context, stream, stream_name, r = context->ops->stream_init(context, stream, stream_name, input_device,
input_device, input_stream_params, output_device,
input_stream_params, output_stream_params, latency, data_callback,
output_device, state_callback, user_ptr);
output_stream_params,
latency,
data_callback,
state_callback,
user_ptr);
if (r == CUBEB_ERROR_INVALID_FORMAT) { if (r == CUBEB_ERROR_INVALID_FORMAT) {
LOG("Invalid format, %p %p %d %d", LOG("Invalid format, %p %p %d %d", output_stream_params,
output_stream_params, input_stream_params, input_stream_params,
output_stream_params && output_stream_params->format, output_stream_params && output_stream_params->format,
input_stream_params && input_stream_params->format); input_stream_params && input_stream_params->format);
} }
@ -392,20 +419,6 @@ cubeb_stream_stop(cubeb_stream * stream)
return stream->context->ops->stream_stop(stream); return stream->context->ops->stream_stop(stream);
} }
int
cubeb_stream_reset_default_device(cubeb_stream * stream)
{
if (!stream) {
return CUBEB_ERROR_INVALID_PARAMETER;
}
if (!stream->context->ops->stream_reset_default_device) {
return CUBEB_ERROR_NOT_SUPPORTED;
}
return stream->context->ops->stream_reset_default_device(stream);
}
int int
cubeb_stream_get_position(cubeb_stream * stream, uint64_t * position) cubeb_stream_get_position(cubeb_stream * stream, uint64_t * position)
{ {
@ -472,8 +485,9 @@ cubeb_stream_set_name(cubeb_stream * stream, char const * stream_name)
return stream->context->ops->stream_set_name(stream, stream_name); return stream->context->ops->stream_set_name(stream, stream_name);
} }
int cubeb_stream_get_current_device(cubeb_stream * stream, int
cubeb_device ** const device) cubeb_stream_get_current_device(cubeb_stream * stream,
cubeb_device ** const device)
{ {
if (!stream || !device) { if (!stream || !device) {
return CUBEB_ERROR_INVALID_PARAMETER; return CUBEB_ERROR_INVALID_PARAMETER;
@ -486,8 +500,8 @@ int cubeb_stream_get_current_device(cubeb_stream * stream,
return stream->context->ops->stream_get_current_device(stream, device); return stream->context->ops->stream_get_current_device(stream, device);
} }
int cubeb_stream_device_destroy(cubeb_stream * stream, int
cubeb_device * device) cubeb_stream_device_destroy(cubeb_stream * stream, cubeb_device * device)
{ {
if (!stream || !device) { if (!stream || !device) {
return CUBEB_ERROR_INVALID_PARAMETER; return CUBEB_ERROR_INVALID_PARAMETER;
@ -500,8 +514,10 @@ int cubeb_stream_device_destroy(cubeb_stream * stream,
return stream->context->ops->stream_device_destroy(stream, device); return stream->context->ops->stream_device_destroy(stream, device);
} }
int cubeb_stream_register_device_changed_callback(cubeb_stream * stream, int
cubeb_device_changed_callback device_changed_callback) cubeb_stream_register_device_changed_callback(
cubeb_stream * stream,
cubeb_device_changed_callback device_changed_callback)
{ {
if (!stream) { if (!stream) {
return CUBEB_ERROR_INVALID_PARAMETER; return CUBEB_ERROR_INVALID_PARAMETER;
@ -511,10 +527,12 @@ int cubeb_stream_register_device_changed_callback(cubeb_stream * stream,
return CUBEB_ERROR_NOT_SUPPORTED; return CUBEB_ERROR_NOT_SUPPORTED;
} }
return stream->context->ops->stream_register_device_changed_callback(stream, device_changed_callback); return stream->context->ops->stream_register_device_changed_callback(
stream, device_changed_callback);
} }
void * cubeb_stream_user_ptr(cubeb_stream * stream) void *
cubeb_stream_user_ptr(cubeb_stream * stream)
{ {
if (!stream) { if (!stream) {
return NULL; return NULL;
@ -523,56 +541,56 @@ void * cubeb_stream_user_ptr(cubeb_stream * stream)
return stream->user_ptr; return stream->user_ptr;
} }
static static void
void log_device(cubeb_device_info * device_info) log_device(cubeb_device_info * device_info)
{ {
char devfmts[128] = ""; char devfmts[128] = "";
const char * devtype, * devstate, * devdeffmt; const char *devtype, *devstate, *devdeffmt;
switch (device_info->type) { switch (device_info->type) {
case CUBEB_DEVICE_TYPE_INPUT: case CUBEB_DEVICE_TYPE_INPUT:
devtype = "input"; devtype = "input";
break; break;
case CUBEB_DEVICE_TYPE_OUTPUT: case CUBEB_DEVICE_TYPE_OUTPUT:
devtype = "output"; devtype = "output";
break; break;
case CUBEB_DEVICE_TYPE_UNKNOWN: case CUBEB_DEVICE_TYPE_UNKNOWN:
default: default:
devtype = "unknown?"; devtype = "unknown?";
break; break;
}; };
switch (device_info->state) { switch (device_info->state) {
case CUBEB_DEVICE_STATE_DISABLED: case CUBEB_DEVICE_STATE_DISABLED:
devstate = "disabled"; devstate = "disabled";
break; break;
case CUBEB_DEVICE_STATE_UNPLUGGED: case CUBEB_DEVICE_STATE_UNPLUGGED:
devstate = "unplugged"; devstate = "unplugged";
break; break;
case CUBEB_DEVICE_STATE_ENABLED: case CUBEB_DEVICE_STATE_ENABLED:
devstate = "enabled"; devstate = "enabled";
break; break;
default: default:
devstate = "unknown?"; devstate = "unknown?";
break; break;
}; };
switch (device_info->default_format) { switch (device_info->default_format) {
case CUBEB_DEVICE_FMT_S16LE: case CUBEB_DEVICE_FMT_S16LE:
devdeffmt = "S16LE"; devdeffmt = "S16LE";
break; break;
case CUBEB_DEVICE_FMT_S16BE: case CUBEB_DEVICE_FMT_S16BE:
devdeffmt = "S16BE"; devdeffmt = "S16BE";
break; break;
case CUBEB_DEVICE_FMT_F32LE: case CUBEB_DEVICE_FMT_F32LE:
devdeffmt = "F32LE"; devdeffmt = "F32LE";
break; break;
case CUBEB_DEVICE_FMT_F32BE: case CUBEB_DEVICE_FMT_F32BE:
devdeffmt = "F32BE"; devdeffmt = "F32BE";
break; break;
default: default:
devdeffmt = "unknown?"; devdeffmt = "unknown?";
break; break;
}; };
if (device_info->format & CUBEB_DEVICE_FMT_S16LE) { if (device_info->format & CUBEB_DEVICE_FMT_S16LE) {
@ -599,20 +617,17 @@ void log_device(cubeb_device_info * device_info)
"\tRate:\t[%u, %u] (default: %u)\n" "\tRate:\t[%u, %u] (default: %u)\n"
"\tLatency: lo %u frames, hi %u frames", "\tLatency: lo %u frames, hi %u frames",
device_info->device_id, device_info->preferred ? " (PREFERRED)" : "", device_info->device_id, device_info->preferred ? " (PREFERRED)" : "",
device_info->friendly_name, device_info->friendly_name, device_info->group_id,
device_info->group_id, device_info->vendor_name, devtype, devstate, device_info->max_channels,
device_info->vendor_name, (devfmts[0] == '\0') ? devfmts : devfmts + 1,
devtype, (unsigned int)device_info->format, devdeffmt, device_info->min_rate,
devstate, device_info->max_rate, device_info->default_rate, device_info->latency_lo,
device_info->max_channels, device_info->latency_hi);
(devfmts[0] == '\0') ? devfmts : devfmts + 1, (unsigned int)device_info->format, devdeffmt,
device_info->min_rate, device_info->max_rate, device_info->default_rate,
device_info->latency_lo, device_info->latency_hi);
} }
int cubeb_enumerate_devices(cubeb * context, int
cubeb_device_type devtype, cubeb_enumerate_devices(cubeb * context, cubeb_device_type devtype,
cubeb_device_collection * collection) cubeb_device_collection * collection)
{ {
int rv; int rv;
if ((devtype & (CUBEB_DEVICE_TYPE_INPUT | CUBEB_DEVICE_TYPE_OUTPUT)) == 0) if ((devtype & (CUBEB_DEVICE_TYPE_INPUT | CUBEB_DEVICE_TYPE_OUTPUT)) == 0)
@ -633,8 +648,9 @@ int cubeb_enumerate_devices(cubeb * context,
return rv; return rv;
} }
int cubeb_device_collection_destroy(cubeb * context, int
cubeb_device_collection * collection) cubeb_device_collection_destroy(cubeb * context,
cubeb_device_collection * collection)
{ {
int r; int r;
@ -656,23 +672,26 @@ int cubeb_device_collection_destroy(cubeb * context,
return r; return r;
} }
int cubeb_register_device_collection_changed(cubeb * context, int
cubeb_device_type devtype, cubeb_register_device_collection_changed(
cubeb_device_collection_changed_callback callback, cubeb * context, cubeb_device_type devtype,
void * user_ptr) cubeb_device_collection_changed_callback callback, void * user_ptr)
{ {
if (context == NULL || (devtype & (CUBEB_DEVICE_TYPE_INPUT | CUBEB_DEVICE_TYPE_OUTPUT)) == 0) if (context == NULL ||
(devtype & (CUBEB_DEVICE_TYPE_INPUT | CUBEB_DEVICE_TYPE_OUTPUT)) == 0)
return CUBEB_ERROR_INVALID_PARAMETER; return CUBEB_ERROR_INVALID_PARAMETER;
if (!context->ops->register_device_collection_changed) { if (!context->ops->register_device_collection_changed) {
return CUBEB_ERROR_NOT_SUPPORTED; return CUBEB_ERROR_NOT_SUPPORTED;
} }
return context->ops->register_device_collection_changed(context, devtype, callback, user_ptr); return context->ops->register_device_collection_changed(context, devtype,
callback, user_ptr);
} }
int cubeb_set_log_callback(cubeb_log_level log_level, int
cubeb_log_callback log_callback) cubeb_set_log_callback(cubeb_log_level log_level,
cubeb_log_callback log_callback)
{ {
if (log_level < CUBEB_LOG_DISABLED || log_level > CUBEB_LOG_VERBOSE) { if (log_level < CUBEB_LOG_DISABLED || log_level > CUBEB_LOG_VERBOSE) {
return CUBEB_ERROR_INVALID_FORMAT; return CUBEB_ERROR_INVALID_FORMAT;
@ -700,4 +719,3 @@ int cubeb_set_log_callback(cubeb_log_level log_level,
return CUBEB_OK; return CUBEB_OK;
} }

1505
externals/cubeb/src/cubeb_aaudio.cpp vendored Executable file

File diff suppressed because it is too large Load diff

View file

@ -8,56 +8,56 @@
#define _DEFAULT_SOURCE #define _DEFAULT_SOURCE
#define _BSD_SOURCE #define _BSD_SOURCE
#define _XOPEN_SOURCE 500 #define _XOPEN_SOURCE 500
#include <pthread.h> #include "cubeb-internal.h"
#include <sys/time.h> #include "cubeb/cubeb.h"
#include <alsa/asoundlib.h>
#include <assert.h> #include <assert.h>
#include <dlfcn.h>
#include <limits.h> #include <limits.h>
#include <poll.h> #include <poll.h>
#include <pthread.h>
#include <sys/time.h>
#include <unistd.h> #include <unistd.h>
#include <dlfcn.h>
#include <alsa/asoundlib.h>
#include "cubeb/cubeb.h"
#include "cubeb-internal.h"
#ifdef DISABLE_LIBASOUND_DLOPEN #ifdef DISABLE_LIBASOUND_DLOPEN
#define WRAP(x) x #define WRAP(x) x
#else #else
#define WRAP(x) cubeb_##x #define WRAP(x) (*cubeb_##x)
#define LIBASOUND_API_VISIT(X) \ #define LIBASOUND_API_VISIT(X) \
X(snd_config) \ X(snd_config) \
X(snd_config_add) \ X(snd_config_add) \
X(snd_config_copy) \ X(snd_config_copy) \
X(snd_config_delete) \ X(snd_config_delete) \
X(snd_config_get_id) \ X(snd_config_get_id) \
X(snd_config_get_string) \ X(snd_config_get_string) \
X(snd_config_imake_integer) \ X(snd_config_imake_integer) \
X(snd_config_search) \ X(snd_config_search) \
X(snd_config_search_definition) \ X(snd_config_search_definition) \
X(snd_lib_error_set_handler) \ X(snd_lib_error_set_handler) \
X(snd_pcm_avail_update) \ X(snd_pcm_avail_update) \
X(snd_pcm_close) \ X(snd_pcm_close) \
X(snd_pcm_delay) \ X(snd_pcm_delay) \
X(snd_pcm_drain) \ X(snd_pcm_drain) \
X(snd_pcm_frames_to_bytes) \ X(snd_pcm_frames_to_bytes) \
X(snd_pcm_get_params) \ X(snd_pcm_get_params) \
X(snd_pcm_hw_params_any) \ X(snd_pcm_hw_params_any) \
X(snd_pcm_hw_params_get_channels_max) \ X(snd_pcm_hw_params_get_channels_max) \
X(snd_pcm_hw_params_get_rate) \ X(snd_pcm_hw_params_get_rate) \
X(snd_pcm_hw_params_set_rate_near) \ X(snd_pcm_hw_params_set_rate_near) \
X(snd_pcm_hw_params_sizeof) \ X(snd_pcm_hw_params_sizeof) \
X(snd_pcm_nonblock) \ X(snd_pcm_nonblock) \
X(snd_pcm_open) \ X(snd_pcm_open) \
X(snd_pcm_open_lconf) \ X(snd_pcm_open_lconf) \
X(snd_pcm_pause) \ X(snd_pcm_pause) \
X(snd_pcm_poll_descriptors) \ X(snd_pcm_poll_descriptors) \
X(snd_pcm_poll_descriptors_count) \ X(snd_pcm_poll_descriptors_count) \
X(snd_pcm_poll_descriptors_revents) \ X(snd_pcm_poll_descriptors_revents) \
X(snd_pcm_readi) \ X(snd_pcm_readi) \
X(snd_pcm_recover) \ X(snd_pcm_recover) \
X(snd_pcm_set_params) \ X(snd_pcm_set_params) \
X(snd_pcm_start) \ X(snd_pcm_start) \
X(snd_pcm_state) \ X(snd_pcm_state) \
X(snd_pcm_writei) \ X(snd_pcm_writei)
#define MAKE_TYPEDEF(x) static typeof(x) * cubeb_##x; #define MAKE_TYPEDEF(x) static typeof(x) * cubeb_##x;
LIBASOUND_API_VISIT(MAKE_TYPEDEF); LIBASOUND_API_VISIT(MAKE_TYPEDEF);
@ -101,7 +101,8 @@ struct cubeb {
int shutdown; int shutdown;
/* Control pipe for forcing poll to wake and rebuild fds or recalculate the timeout. */ /* Control pipe for forcing poll to wake and rebuild fds or recalculate the
* timeout. */
int control_fd_read; int control_fd_read;
int control_fd_write; int control_fd_write;
@ -116,13 +117,7 @@ struct cubeb {
int is_pa; int is_pa;
}; };
enum stream_state { enum stream_state { INACTIVE, RUNNING, DRAINING, PROCESSING, ERROR };
INACTIVE,
RUNNING,
DRAINING,
PROCESSING,
ERROR
};
struct cubeb_stream { struct cubeb_stream {
/* Note: Must match cubeb_stream layout in cubeb.c. */ /* Note: Must match cubeb_stream layout in cubeb.c. */
@ -146,7 +141,8 @@ struct cubeb_stream {
enum stream_state state; enum stream_state state;
struct pollfd * saved_fds; /* A copy of the pollfds passed in at init time. */ struct pollfd * saved_fds; /* A copy of the pollfds passed in at init time. */
struct pollfd * fds; /* Pointer to this waitable's pollfds within struct cubeb's fds. */ struct pollfd *
fds; /* Pointer to this waitable's pollfds within struct cubeb's fds. */
nfds_t nfds; nfds_t nfds;
struct timeval drain_timeout; struct timeval drain_timeout;
@ -294,8 +290,10 @@ set_timeout(struct timeval * timeout, unsigned int ms)
static void static void
stream_buffer_decrement(cubeb_stream * stm, long count) stream_buffer_decrement(cubeb_stream * stm, long count)
{ {
char * bufremains = stm->buffer + WRAP(snd_pcm_frames_to_bytes)(stm->pcm, count); char * bufremains =
memmove(stm->buffer, bufremains, WRAP(snd_pcm_frames_to_bytes)(stm->pcm, stm->bufframes - count)); stm->buffer + WRAP(snd_pcm_frames_to_bytes)(stm->pcm, count);
memmove(stm->buffer, bufremains,
WRAP(snd_pcm_frames_to_bytes)(stm->pcm, stm->bufframes - count));
stm->bufframes -= count; stm->bufframes -= count;
} }
@ -327,7 +325,8 @@ alsa_process_stream(cubeb_stream * stm)
/* Call _poll_descriptors_revents() even if we don't use it /* Call _poll_descriptors_revents() even if we don't use it
to let underlying plugins clear null events. Otherwise poll() to let underlying plugins clear null events. Otherwise poll()
may wake up again and again, producing unnecessary CPU usage. */ may wake up again and again, producing unnecessary CPU usage. */
WRAP(snd_pcm_poll_descriptors_revents)(stm->pcm, stm->fds, stm->nfds, &revents); WRAP(snd_pcm_poll_descriptors_revents)
(stm->pcm, stm->fds, stm->nfds, &revents);
avail = WRAP(snd_pcm_avail_update)(stm->pcm); avail = WRAP(snd_pcm_avail_update)(stm->pcm);
@ -337,8 +336,9 @@ alsa_process_stream(cubeb_stream * stm)
return RUNNING; return RUNNING;
} }
/* This could happen if we were suspended with SIGSTOP/Ctrl+Z for a long time. */ /* This could happen if we were suspended with SIGSTOP/Ctrl+Z for a long time.
if ((unsigned int) avail > stm->buffer_size) { */
if ((unsigned int)avail > stm->buffer_size) {
avail = stm->buffer_size; avail = stm->buffer_size;
} }
@ -352,7 +352,7 @@ alsa_process_stream(cubeb_stream * stm)
// TODO: should it be marked as DRAINING? // TODO: should it be marked as DRAINING?
} }
got = WRAP(snd_pcm_readi)(stm->pcm, stm->buffer+stm->bufframes, avail); got = WRAP(snd_pcm_readi)(stm->pcm, stm->buffer + stm->bufframes, avail);
if (got < 0) { if (got < 0) {
avail = got; // the error handler below will recover us avail = got; // the error handler below will recover us
@ -366,18 +366,24 @@ alsa_process_stream(cubeb_stream * stm)
/* Capture: Pass read frames to callback function */ /* Capture: Pass read frames to callback function */
if (stm->stream_type == SND_PCM_STREAM_CAPTURE && stm->bufframes > 0 && if (stm->stream_type == SND_PCM_STREAM_CAPTURE && stm->bufframes > 0 &&
(!stm->other_stream || stm->other_stream->bufframes < stm->other_stream->buffer_size)) { (!stm->other_stream ||
stm->other_stream->bufframes < stm->other_stream->buffer_size)) {
snd_pcm_sframes_t wrote = stm->bufframes; snd_pcm_sframes_t wrote = stm->bufframes;
struct cubeb_stream * mainstm = stm->other_stream ? stm->other_stream : stm; struct cubeb_stream * mainstm = stm->other_stream ? stm->other_stream : stm;
void * other_buffer = stm->other_stream ? stm->other_stream->buffer + stm->other_stream->bufframes : NULL; void * other_buffer = stm->other_stream ? stm->other_stream->buffer +
stm->other_stream->bufframes
: NULL;
/* Correct write size to the other stream available space */ /* Correct write size to the other stream available space */
if (stm->other_stream && wrote > (snd_pcm_sframes_t) (stm->other_stream->buffer_size - stm->other_stream->bufframes)) { if (stm->other_stream &&
wrote > (snd_pcm_sframes_t)(stm->other_stream->buffer_size -
stm->other_stream->bufframes)) {
wrote = stm->other_stream->buffer_size - stm->other_stream->bufframes; wrote = stm->other_stream->buffer_size - stm->other_stream->bufframes;
} }
pthread_mutex_unlock(&stm->mutex); pthread_mutex_unlock(&stm->mutex);
wrote = stm->data_callback(mainstm, stm->user_ptr, stm->buffer, other_buffer, wrote); wrote = stm->data_callback(mainstm, stm->user_ptr, stm->buffer,
other_buffer, wrote);
pthread_mutex_lock(&stm->mutex); pthread_mutex_lock(&stm->mutex);
if (wrote < 0) { if (wrote < 0) {
@ -392,14 +398,17 @@ alsa_process_stream(cubeb_stream * stm)
} }
/* Playback: Don't have enough data? Let's ask for more. */ /* Playback: Don't have enough data? Let's ask for more. */
if (stm->stream_type == SND_PCM_STREAM_PLAYBACK && avail > (snd_pcm_sframes_t) stm->bufframes && if (stm->stream_type == SND_PCM_STREAM_PLAYBACK &&
avail > (snd_pcm_sframes_t)stm->bufframes &&
(!stm->other_stream || stm->other_stream->bufframes > 0)) { (!stm->other_stream || stm->other_stream->bufframes > 0)) {
long got = avail - stm->bufframes; long got = avail - stm->bufframes;
void * other_buffer = stm->other_stream ? stm->other_stream->buffer : NULL; void * other_buffer = stm->other_stream ? stm->other_stream->buffer : NULL;
char * buftail = stm->buffer + WRAP(snd_pcm_frames_to_bytes)(stm->pcm, stm->bufframes); char * buftail =
stm->buffer + WRAP(snd_pcm_frames_to_bytes)(stm->pcm, stm->bufframes);
/* Correct read size to the other stream available frames */ /* Correct read size to the other stream available frames */
if (stm->other_stream && got > (snd_pcm_sframes_t) stm->other_stream->bufframes) { if (stm->other_stream &&
got > (snd_pcm_sframes_t)stm->other_stream->bufframes) {
got = stm->other_stream->bufframes; got = stm->other_stream->bufframes;
} }
@ -419,11 +428,13 @@ alsa_process_stream(cubeb_stream * stm)
} }
/* Playback: Still don't have enough data? Add some silence. */ /* Playback: Still don't have enough data? Add some silence. */
if (stm->stream_type == SND_PCM_STREAM_PLAYBACK && avail > (snd_pcm_sframes_t) stm->bufframes) { if (stm->stream_type == SND_PCM_STREAM_PLAYBACK &&
avail > (snd_pcm_sframes_t)stm->bufframes) {
long drain_frames = avail - stm->bufframes; long drain_frames = avail - stm->bufframes;
double drain_time = (double) drain_frames / stm->params.rate; double drain_time = (double)drain_frames / stm->params.rate;
char * buftail = stm->buffer + WRAP(snd_pcm_frames_to_bytes)(stm->pcm, stm->bufframes); char * buftail =
stm->buffer + WRAP(snd_pcm_frames_to_bytes)(stm->pcm, stm->bufframes);
memset(buftail, 0, WRAP(snd_pcm_frames_to_bytes)(stm->pcm, drain_frames)); memset(buftail, 0, WRAP(snd_pcm_frames_to_bytes)(stm->pcm, drain_frames));
stm->bufframes = avail; stm->bufframes = avail;
@ -440,12 +451,12 @@ alsa_process_stream(cubeb_stream * stm)
snd_pcm_sframes_t wrote; snd_pcm_sframes_t wrote;
if (stm->params.format == CUBEB_SAMPLE_FLOAT32NE) { if (stm->params.format == CUBEB_SAMPLE_FLOAT32NE) {
float * b = (float *) stm->buffer; float * b = (float *)stm->buffer;
for (uint32_t i = 0; i < avail * stm->params.channels; i++) { for (uint32_t i = 0; i < avail * stm->params.channels; i++) {
b[i] *= stm->volume; b[i] *= stm->volume;
} }
} else { } else {
short * b = (short *) stm->buffer; short * b = (short *)stm->buffer;
for (uint32_t i = 0; i < avail * stm->params.channels; i++) { for (uint32_t i = 0; i < avail * stm->params.channels; i++) {
b[i] *= stm->volume; b[i] *= stm->volume;
} }
@ -467,8 +478,7 @@ alsa_process_stream(cubeb_stream * stm)
avail = WRAP(snd_pcm_recover)(stm->pcm, avail, 0); avail = WRAP(snd_pcm_recover)(stm->pcm, avail, 0);
/* Capture pcm must be started after initial setup/recover */ /* Capture pcm must be started after initial setup/recover */
if (avail >= 0 && if (avail >= 0 && stm->stream_type == SND_PCM_STREAM_CAPTURE &&
stm->stream_type == SND_PCM_STREAM_CAPTURE &&
WRAP(snd_pcm_state)(stm->pcm) == SND_PCM_STATE_PREPARED) { WRAP(snd_pcm_state)(stm->pcm) == SND_PCM_STATE_PREPARED) {
avail = WRAP(snd_pcm_start)(stm->pcm); avail = WRAP(snd_pcm_start)(stm->pcm);
} }
@ -533,7 +543,8 @@ alsa_run(cubeb * ctx)
stm = ctx->streams[i]; stm = ctx->streams[i];
/* We can't use snd_pcm_poll_descriptors_revents here because of /* We can't use snd_pcm_poll_descriptors_revents here because of
https://github.com/kinetiknz/cubeb/issues/135. */ https://github.com/kinetiknz/cubeb/issues/135. */
if (stm && stm->state == RUNNING && stm->fds && any_revents(stm->fds, stm->nfds)) { if (stm && stm->state == RUNNING && stm->fds &&
any_revents(stm->fds, stm->nfds)) {
alsa_set_stream_state(stm, PROCESSING); alsa_set_stream_state(stm, PROCESSING);
pthread_mutex_unlock(&ctx->mutex); pthread_mutex_unlock(&ctx->mutex);
state = alsa_process_stream(stm); state = alsa_process_stream(stm);
@ -548,7 +559,8 @@ alsa_run(cubeb * ctx)
if (stm->state == DRAINING && ms_since(&stm->drain_timeout) >= 0) { if (stm->state == DRAINING && ms_since(&stm->drain_timeout) >= 0) {
alsa_set_stream_state(stm, INACTIVE); alsa_set_stream_state(stm, INACTIVE);
stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_DRAINED); stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_DRAINED);
} else if (stm->state == RUNNING && ms_since(&stm->last_activity) > CUBEB_WATCHDOG_MS) { } else if (stm->state == RUNNING &&
ms_since(&stm->last_activity) > CUBEB_WATCHDOG_MS) {
alsa_set_stream_state(stm, ERROR); alsa_set_stream_state(stm, ERROR);
stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR); stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR);
} }
@ -593,7 +605,8 @@ get_slave_pcm_node(snd_config_t * lconf, snd_config_t * root_pcm)
r = WRAP(snd_config_get_string)(slave_pcm, &string); r = WRAP(snd_config_get_string)(slave_pcm, &string);
if (r >= 0) { if (r >= 0) {
r = WRAP(snd_config_search_definition)(lconf, "pcm_slave", string, &slave_def); r = WRAP(snd_config_search_definition)(lconf, "pcm_slave", string,
&slave_def);
if (r < 0) { if (r < 0) {
return NULL; return NULL;
} }
@ -611,7 +624,7 @@ get_slave_pcm_node(snd_config_t * lconf, snd_config_t * root_pcm)
} }
r = snprintf(node_name, sizeof(node_name), "pcm.%s", string); r = snprintf(node_name, sizeof(node_name), "pcm.%s", string);
if (r < 0 || r > (int) sizeof(node_name)) { if (r < 0 || r > (int)sizeof(node_name)) {
break; break;
} }
r = WRAP(snd_config_search)(lconf, node_name, &pcm); r = WRAP(snd_config_search)(lconf, node_name, &pcm);
@ -633,7 +646,8 @@ get_slave_pcm_node(snd_config_t * lconf, snd_config_t * root_pcm)
higher than requested latency, but the plugin does not update its (and higher than requested latency, but the plugin does not update its (and
ALSA's) internal state to reflect that, leading to an immediate underrun ALSA's) internal state to reflect that, leading to an immediate underrun
situation. Inspired by WINE's make_handle_underrun_config. situation. Inspired by WINE's make_handle_underrun_config.
Reference: http://mailman.alsa-project.org/pipermail/alsa-devel/2012-July/05 */ Reference: http://mailman.alsa-project.org/pipermail/alsa-devel/2012-July/05
*/
static snd_config_t * static snd_config_t *
init_local_config_with_workaround(char const * pcm_name) init_local_config_with_workaround(char const * pcm_name)
{ {
@ -646,11 +660,11 @@ init_local_config_with_workaround(char const * pcm_name)
lconf = NULL; lconf = NULL;
if (*WRAP(snd_config) == NULL) { if (WRAP(snd_config) == NULL) {
return NULL; return NULL;
} }
r = WRAP(snd_config_copy)(&lconf, *WRAP(snd_config)); r = WRAP(snd_config_copy)(&lconf, WRAP(snd_config));
if (r < 0) { if (r < 0) {
return NULL; return NULL;
} }
@ -667,7 +681,7 @@ init_local_config_with_workaround(char const * pcm_name)
} }
r = snprintf(node_name, sizeof(node_name), "pcm.%s", string); r = snprintf(node_name, sizeof(node_name), "pcm.%s", string);
if (r < 0 || r > (int) sizeof(node_name)) { if (r < 0 || r > (int)sizeof(node_name)) {
break; break;
} }
r = WRAP(snd_config_search)(lconf, node_name, &pcm_node); r = WRAP(snd_config_search)(lconf, node_name, &pcm_node);
@ -675,12 +689,14 @@ init_local_config_with_workaround(char const * pcm_name)
break; break;
} }
/* If this PCM has a slave, walk the slave configurations until we reach the bottom. */ /* If this PCM has a slave, walk the slave configurations until we reach the
* bottom. */
while ((node = get_slave_pcm_node(lconf, pcm_node)) != NULL) { while ((node = get_slave_pcm_node(lconf, pcm_node)) != NULL) {
pcm_node = node; pcm_node = node;
} }
/* Fetch the PCM node's type, and bail out if it's not the PulseAudio plugin. */ /* Fetch the PCM node's type, and bail out if it's not the PulseAudio
* plugin. */
r = WRAP(snd_config_search)(pcm_node, "type", &node); r = WRAP(snd_config_search)(pcm_node, "type", &node);
if (r < 0) { if (r < 0) {
break; break;
@ -722,13 +738,15 @@ init_local_config_with_workaround(char const * pcm_name)
} }
static int static int
alsa_locked_pcm_open(snd_pcm_t ** pcm, char const * pcm_name, snd_pcm_stream_t stream, snd_config_t * local_config) alsa_locked_pcm_open(snd_pcm_t ** pcm, char const * pcm_name,
snd_pcm_stream_t stream, snd_config_t * local_config)
{ {
int r; int r;
pthread_mutex_lock(&cubeb_alsa_mutex); pthread_mutex_lock(&cubeb_alsa_mutex);
if (local_config) { if (local_config) {
r = WRAP(snd_pcm_open_lconf)(pcm, pcm_name, stream, SND_PCM_NONBLOCK, local_config); r = WRAP(snd_pcm_open_lconf)(pcm, pcm_name, stream, SND_PCM_NONBLOCK,
local_config);
} else { } else {
r = WRAP(snd_pcm_open)(pcm, pcm_name, stream, SND_PCM_NONBLOCK); r = WRAP(snd_pcm_open)(pcm, pcm_name, stream, SND_PCM_NONBLOCK);
} }
@ -819,12 +837,13 @@ alsa_init(cubeb ** context, char const * context_name)
} }
} }
#define LOAD(x) { \ #define LOAD(x) \
cubeb_##x = dlsym(libasound, #x); \ { \
if (!cubeb_##x) { \ cubeb_##x = dlsym(libasound, #x); \
dlclose(libasound); \ if (!cubeb_##x) { \
return CUBEB_ERROR; \ dlclose(libasound); \
} \ return CUBEB_ERROR; \
} \
} }
LIBASOUND_API_VISIT(LOAD); LIBASOUND_API_VISIT(LOAD);
@ -876,7 +895,8 @@ alsa_init(cubeb ** context, char const * context_name)
/* Open a dummy PCM to force the configuration space to be evaluated so that /* Open a dummy PCM to force the configuration space to be evaluated so that
init_local_config_with_workaround can find and modify the default node. */ init_local_config_with_workaround can find and modify the default node. */
r = alsa_locked_pcm_open(&dummy, CUBEB_ALSA_PCM_NAME, SND_PCM_STREAM_PLAYBACK, NULL); r = alsa_locked_pcm_open(&dummy, CUBEB_ALSA_PCM_NAME, SND_PCM_STREAM_PLAYBACK,
NULL);
if (r >= 0) { if (r >= 0) {
alsa_locked_pcm_close(dummy); alsa_locked_pcm_close(dummy);
} }
@ -886,7 +906,8 @@ alsa_init(cubeb ** context, char const * context_name)
pthread_mutex_unlock(&cubeb_alsa_mutex); pthread_mutex_unlock(&cubeb_alsa_mutex);
if (ctx->local_config) { if (ctx->local_config) {
ctx->is_pa = 1; ctx->is_pa = 1;
r = alsa_locked_pcm_open(&dummy, CUBEB_ALSA_PCM_NAME, SND_PCM_STREAM_PLAYBACK, ctx->local_config); r = alsa_locked_pcm_open(&dummy, CUBEB_ALSA_PCM_NAME,
SND_PCM_STREAM_PLAYBACK, ctx->local_config);
/* If we got a local_config, we found a PA PCM. If opening a PCM with that /* If we got a local_config, we found a PA PCM. If opening a PCM with that
config fails with EINVAL, the PA PCM is too old for this workaround. */ config fails with EINVAL, the PA PCM is too old for this workaround. */
if (r == -EINVAL) { if (r == -EINVAL) {
@ -944,17 +965,17 @@ alsa_destroy(cubeb * ctx)
free(ctx); free(ctx);
} }
static void alsa_stream_destroy(cubeb_stream * stm); static void
alsa_stream_destroy(cubeb_stream * stm);
static int static int
alsa_stream_init_single(cubeb * ctx, cubeb_stream ** stream, char const * stream_name, alsa_stream_init_single(cubeb * ctx, cubeb_stream ** stream,
snd_pcm_stream_t stream_type, char const * stream_name, snd_pcm_stream_t stream_type,
cubeb_devid deviceid, cubeb_devid deviceid,
cubeb_stream_params * stream_params, cubeb_stream_params * stream_params,
unsigned int latency_frames, unsigned int latency_frames,
cubeb_data_callback data_callback, cubeb_data_callback data_callback,
cubeb_state_callback state_callback, cubeb_state_callback state_callback, void * user_ptr)
void * user_ptr)
{ {
(void)stream_name; (void)stream_name;
cubeb_stream * stm; cubeb_stream * stm;
@ -962,7 +983,8 @@ alsa_stream_init_single(cubeb * ctx, cubeb_stream ** stream, char const * stream
snd_pcm_format_t format; snd_pcm_format_t format;
snd_pcm_uframes_t period_size; snd_pcm_uframes_t period_size;
int latency_us = 0; int latency_us = 0;
char const * pcm_name = deviceid ? (char const *) deviceid : CUBEB_ALSA_PCM_NAME; char const * pcm_name =
deviceid ? (char const *)deviceid : CUBEB_ALSA_PCM_NAME;
assert(ctx && stream); assert(ctx && stream);
@ -1018,7 +1040,8 @@ alsa_stream_init_single(cubeb * ctx, cubeb_stream ** stream, char const * stream
r = pthread_cond_init(&stm->cond, NULL); r = pthread_cond_init(&stm->cond, NULL);
assert(r == 0); assert(r == 0);
r = alsa_locked_pcm_open(&stm->pcm, pcm_name, stm->stream_type, ctx->local_config); r = alsa_locked_pcm_open(&stm->pcm, pcm_name, stm->stream_type,
ctx->local_config);
if (r < 0) { if (r < 0) {
alsa_stream_destroy(stm); alsa_stream_destroy(stm);
return CUBEB_ERROR; return CUBEB_ERROR;
@ -1034,12 +1057,12 @@ alsa_stream_init_single(cubeb * ctx, cubeb_stream ** stream, char const * stream
Only resort to this hack if the handle_underrun workaround failed. */ Only resort to this hack if the handle_underrun workaround failed. */
if (!ctx->local_config && ctx->is_pa) { if (!ctx->local_config && ctx->is_pa) {
const int min_latency = 5e5; const int min_latency = 5e5;
latency_us = latency_us < min_latency ? min_latency: latency_us; latency_us = latency_us < min_latency ? min_latency : latency_us;
} }
r = WRAP(snd_pcm_set_params)(stm->pcm, format, SND_PCM_ACCESS_RW_INTERLEAVED, r = WRAP(snd_pcm_set_params)(stm->pcm, format, SND_PCM_ACCESS_RW_INTERLEAVED,
stm->params.channels, stm->params.rate, 1, stm->params.channels, stm->params.rate, 1,
latency_us); latency_us);
if (r < 0) { if (r < 0) {
alsa_stream_destroy(stm); alsa_stream_destroy(stm);
return CUBEB_ERROR_INVALID_FORMAT; return CUBEB_ERROR_INVALID_FORMAT;
@ -1048,9 +1071,11 @@ alsa_stream_init_single(cubeb * ctx, cubeb_stream ** stream, char const * stream
r = WRAP(snd_pcm_get_params)(stm->pcm, &stm->buffer_size, &period_size); r = WRAP(snd_pcm_get_params)(stm->pcm, &stm->buffer_size, &period_size);
assert(r == 0); assert(r == 0);
/* Double internal buffer size to have enough space when waiting for the other side of duplex connection */ /* Double internal buffer size to have enough space when waiting for the other
* side of duplex connection */
stm->buffer_size *= 2; stm->buffer_size *= 2;
stm->buffer = calloc(1, WRAP(snd_pcm_frames_to_bytes)(stm->pcm, stm->buffer_size)); stm->buffer =
calloc(1, WRAP(snd_pcm_frames_to_bytes)(stm->pcm, stm->buffer_size));
assert(stm->buffer); assert(stm->buffer);
stm->nfds = WRAP(snd_pcm_poll_descriptors_count)(stm->pcm); stm->nfds = WRAP(snd_pcm_poll_descriptors_count)(stm->pcm);
@ -1059,7 +1084,7 @@ alsa_stream_init_single(cubeb * ctx, cubeb_stream ** stream, char const * stream
stm->saved_fds = calloc(stm->nfds, sizeof(struct pollfd)); stm->saved_fds = calloc(stm->nfds, sizeof(struct pollfd));
assert(stm->saved_fds); assert(stm->saved_fds);
r = WRAP(snd_pcm_poll_descriptors)(stm->pcm, stm->saved_fds, stm->nfds); r = WRAP(snd_pcm_poll_descriptors)(stm->pcm, stm->saved_fds, stm->nfds);
assert((nfds_t) r == stm->nfds); assert((nfds_t)r == stm->nfds);
if (alsa_register_stream(ctx, stm) != 0) { if (alsa_register_stream(ctx, stm) != 0) {
alsa_stream_destroy(stm); alsa_stream_destroy(stm);
@ -1077,22 +1102,23 @@ alsa_stream_init(cubeb * ctx, cubeb_stream ** stream, char const * stream_name,
cubeb_stream_params * input_stream_params, cubeb_stream_params * input_stream_params,
cubeb_devid output_device, cubeb_devid output_device,
cubeb_stream_params * output_stream_params, cubeb_stream_params * output_stream_params,
unsigned int latency_frames, unsigned int latency_frames, cubeb_data_callback data_callback,
cubeb_data_callback data_callback, cubeb_state_callback state_callback, cubeb_state_callback state_callback, void * user_ptr)
void * user_ptr)
{ {
int result = CUBEB_OK; int result = CUBEB_OK;
cubeb_stream * instm = NULL, * outstm = NULL; cubeb_stream *instm = NULL, *outstm = NULL;
if (result == CUBEB_OK && input_stream_params) { if (result == CUBEB_OK && input_stream_params) {
result = alsa_stream_init_single(ctx, &instm, stream_name, SND_PCM_STREAM_CAPTURE, result = alsa_stream_init_single(ctx, &instm, stream_name,
input_device, input_stream_params, latency_frames, SND_PCM_STREAM_CAPTURE, input_device,
input_stream_params, latency_frames,
data_callback, state_callback, user_ptr); data_callback, state_callback, user_ptr);
} }
if (result == CUBEB_OK && output_stream_params) { if (result == CUBEB_OK && output_stream_params) {
result = alsa_stream_init_single(ctx, &outstm, stream_name, SND_PCM_STREAM_PLAYBACK, result = alsa_stream_init_single(ctx, &outstm, stream_name,
output_device, output_stream_params, latency_frames, SND_PCM_STREAM_PLAYBACK, output_device,
output_stream_params, latency_frames,
data_callback, state_callback, user_ptr); data_callback, state_callback, user_ptr);
} }
@ -1116,8 +1142,7 @@ alsa_stream_destroy(cubeb_stream * stm)
int r; int r;
cubeb * ctx; cubeb * ctx;
assert(stm && (stm->state == INACTIVE || assert(stm && (stm->state == INACTIVE || stm->state == ERROR ||
stm->state == ERROR ||
stm->state == DRAINING)); stm->state == DRAINING));
ctx = stm->context; ctx = stm->context;
@ -1159,7 +1184,7 @@ alsa_get_max_channel_count(cubeb * ctx, uint32_t * max_channels)
{ {
int r; int r;
cubeb_stream * stm; cubeb_stream * stm;
snd_pcm_hw_params_t* hw_params; snd_pcm_hw_params_t * hw_params;
cubeb_stream_params params; cubeb_stream_params params;
params.rate = 44100; params.rate = 44100;
params.format = CUBEB_SAMPLE_FLOAT32NE; params.format = CUBEB_SAMPLE_FLOAT32NE;
@ -1169,7 +1194,8 @@ alsa_get_max_channel_count(cubeb * ctx, uint32_t * max_channels)
assert(ctx); assert(ctx);
r = alsa_stream_init(ctx, &stm, "", NULL, NULL, NULL, &params, 100, NULL, NULL, NULL); r = alsa_stream_init(ctx, &stm, "", NULL, NULL, NULL, &params, 100, NULL,
NULL, NULL);
if (r != CUBEB_OK) { if (r != CUBEB_OK) {
return CUBEB_ERROR; return CUBEB_ERROR;
} }
@ -1192,7 +1218,8 @@ alsa_get_max_channel_count(cubeb * ctx, uint32_t * max_channels)
} }
static int static int
alsa_get_preferred_sample_rate(cubeb * ctx, uint32_t * rate) { alsa_get_preferred_sample_rate(cubeb * ctx, uint32_t * rate)
{
(void)ctx; (void)ctx;
int r, dir; int r, dir;
snd_pcm_t * pcm; snd_pcm_t * pcm;
@ -1202,7 +1229,8 @@ alsa_get_preferred_sample_rate(cubeb * ctx, uint32_t * rate) {
/* get a pcm, disabling resampling, so we get a rate the /* get a pcm, disabling resampling, so we get a rate the
* hardware/dmix/pulse/etc. supports. */ * hardware/dmix/pulse/etc. supports. */
r = WRAP(snd_pcm_open)(&pcm, CUBEB_ALSA_PCM_NAME, SND_PCM_STREAM_PLAYBACK, SND_PCM_NO_AUTO_RESAMPLE); r = WRAP(snd_pcm_open)(&pcm, CUBEB_ALSA_PCM_NAME, SND_PCM_STREAM_PLAYBACK,
SND_PCM_NO_AUTO_RESAMPLE);
if (r < 0) { if (r < 0) {
return CUBEB_ERROR; return CUBEB_ERROR;
} }
@ -1235,7 +1263,8 @@ alsa_get_preferred_sample_rate(cubeb * ctx, uint32_t * rate) {
} }
static int static int
alsa_get_min_latency(cubeb * ctx, cubeb_stream_params params, uint32_t * latency_frames) alsa_get_min_latency(cubeb * ctx, cubeb_stream_params params,
uint32_t * latency_frames)
{ {
(void)ctx; (void)ctx;
/* 40ms is found to be an acceptable minimum, even on a super low-end /* 40ms is found to be an acceptable minimum, even on a super low-end
@ -1331,7 +1360,7 @@ alsa_stream_get_position(cubeb_stream * stm, uint64_t * position)
assert(delay >= 0); assert(delay >= 0);
*position = 0; *position = 0;
if (stm->stream_position >= (snd_pcm_uframes_t) delay) { if (stm->stream_position >= (snd_pcm_uframes_t)delay) {
*position = stm->stream_position - delay; *position = stm->stream_position - delay;
} }
@ -1346,7 +1375,8 @@ alsa_stream_get_latency(cubeb_stream * stm, uint32_t * latency)
{ {
snd_pcm_sframes_t delay; snd_pcm_sframes_t delay;
/* This function returns the delay in frames until a frame written using /* This function returns the delay in frames until a frame written using
snd_pcm_writei is sent to the DAC. The DAC delay should be < 1ms anyways. */ snd_pcm_writei is sent to the DAC. The DAC delay should be < 1ms anyways.
*/
if (WRAP(snd_pcm_delay)(stm->pcm, &delay)) { if (WRAP(snd_pcm_delay)(stm->pcm, &delay)) {
return CUBEB_ERROR; return CUBEB_ERROR;
} }
@ -1371,7 +1401,7 @@ static int
alsa_enumerate_devices(cubeb * context, cubeb_device_type type, alsa_enumerate_devices(cubeb * context, cubeb_device_type type,
cubeb_device_collection * collection) cubeb_device_collection * collection)
{ {
cubeb_device_info* device = NULL; cubeb_device_info * device = NULL;
if (!context) if (!context)
return CUBEB_ERROR; return CUBEB_ERROR;
@ -1390,13 +1420,13 @@ alsa_enumerate_devices(cubeb * context, cubeb_device_type type,
} }
char const * a_name = "default"; char const * a_name = "default";
device = (cubeb_device_info *) calloc(1, sizeof(cubeb_device_info)); device = (cubeb_device_info *)calloc(1, sizeof(cubeb_device_info));
assert(device); assert(device);
if (!device) if (!device)
return CUBEB_ERROR; return CUBEB_ERROR;
device->device_id = a_name; device->device_id = a_name;
device->devid = (cubeb_devid) device->device_id; device->devid = (cubeb_devid)device->device_id;
device->friendly_name = a_name; device->friendly_name = a_name;
device->group_id = a_name; device->group_id = a_name;
device->vendor_name = a_name; device->vendor_name = a_name;
@ -1423,32 +1453,30 @@ alsa_device_collection_destroy(cubeb * context,
cubeb_device_collection * collection) cubeb_device_collection * collection)
{ {
assert(collection->count == 1); assert(collection->count == 1);
(void) context; (void)context;
free(collection->device); free(collection->device);
return CUBEB_OK; return CUBEB_OK;
} }
static struct cubeb_ops const alsa_ops = { static struct cubeb_ops const alsa_ops = {
.init = alsa_init, .init = alsa_init,
.get_backend_id = alsa_get_backend_id, .get_backend_id = alsa_get_backend_id,
.get_max_channel_count = alsa_get_max_channel_count, .get_max_channel_count = alsa_get_max_channel_count,
.get_min_latency = alsa_get_min_latency, .get_min_latency = alsa_get_min_latency,
.get_preferred_sample_rate = alsa_get_preferred_sample_rate, .get_preferred_sample_rate = alsa_get_preferred_sample_rate,
.enumerate_devices = alsa_enumerate_devices, .enumerate_devices = alsa_enumerate_devices,
.device_collection_destroy = alsa_device_collection_destroy, .device_collection_destroy = alsa_device_collection_destroy,
.destroy = alsa_destroy, .destroy = alsa_destroy,
.stream_init = alsa_stream_init, .stream_init = alsa_stream_init,
.stream_destroy = alsa_stream_destroy, .stream_destroy = alsa_stream_destroy,
.stream_start = alsa_stream_start, .stream_start = alsa_stream_start,
.stream_stop = alsa_stream_stop, .stream_stop = alsa_stream_stop,
.stream_reset_default_device = NULL, .stream_get_position = alsa_stream_get_position,
.stream_get_position = alsa_stream_get_position, .stream_get_latency = alsa_stream_get_latency,
.stream_get_latency = alsa_stream_get_latency, .stream_get_input_latency = NULL,
.stream_get_input_latency = NULL, .stream_set_volume = alsa_stream_set_volume,
.stream_set_volume = alsa_stream_set_volume, .stream_set_name = NULL,
.stream_set_name = NULL, .stream_get_current_device = NULL,
.stream_get_current_device = NULL, .stream_device_destroy = NULL,
.stream_device_destroy = NULL, .stream_register_device_changed_callback = NULL,
.stream_register_device_changed_callback = NULL, .register_device_collection_changed = NULL};
.register_device_collection_changed = NULL
};

17
externals/cubeb/src/cubeb_android.h vendored Executable file
View file

@ -0,0 +1,17 @@
#ifndef CUBEB_ANDROID_H
#define CUBEB_ANDROID_H
#ifdef __cplusplus
extern "C" {
#endif
// If the latency requested is above this threshold, this stream is considered
// intended for playback (vs. real-time). Tell Android it should favor saving
// power over performance or latency.
// This is around 100ms at 44100 or 48000
const uint16_t POWERSAVE_LATENCY_FRAMES_THRESHOLD = 4000;
#ifdef __cplusplus
};
#endif
#endif // CUBEB_ANDROID_H

View file

@ -16,8 +16,7 @@
extern "C" { extern "C" {
#endif #endif
typedef struct typedef struct {
{
void ** buf; void ** buf;
size_t num; size_t num;
size_t writePos; size_t writePos;
@ -25,10 +24,11 @@ typedef struct
pthread_mutex_t mutex; pthread_mutex_t mutex;
} array_queue; } array_queue;
array_queue * array_queue_create(size_t num) array_queue *
array_queue_create(size_t num)
{ {
assert(num != 0); assert(num != 0);
array_queue * new_queue = (array_queue*)calloc(1, sizeof(array_queue)); array_queue * new_queue = (array_queue *)calloc(1, sizeof(array_queue));
new_queue->buf = (void **)calloc(1, sizeof(void *) * num); new_queue->buf = (void **)calloc(1, sizeof(void *) * num);
new_queue->readPos = 0; new_queue->readPos = 0;
new_queue->writePos = 0; new_queue->writePos = 0;
@ -39,7 +39,8 @@ array_queue * array_queue_create(size_t num)
return new_queue; return new_queue;
} }
void array_queue_destroy(array_queue * aq) void
array_queue_destroy(array_queue * aq)
{ {
assert(aq); assert(aq);
@ -48,14 +49,14 @@ void array_queue_destroy(array_queue * aq)
free(aq); free(aq);
} }
int array_queue_push(array_queue * aq, void * item) int
array_queue_push(array_queue * aq, void * item)
{ {
assert(item); assert(item);
pthread_mutex_lock(&aq->mutex); pthread_mutex_lock(&aq->mutex);
int ret = -1; int ret = -1;
if(aq->buf[aq->writePos % aq->num] == NULL) if (aq->buf[aq->writePos % aq->num] == NULL) {
{
aq->buf[aq->writePos % aq->num] = item; aq->buf[aq->writePos % aq->num] = item;
aq->writePos = (aq->writePos + 1) % aq->num; aq->writePos = (aq->writePos + 1) % aq->num;
ret = 0; ret = 0;
@ -65,12 +66,12 @@ int array_queue_push(array_queue * aq, void * item)
return ret; return ret;
} }
void* array_queue_pop(array_queue * aq) void *
array_queue_pop(array_queue * aq)
{ {
pthread_mutex_lock(&aq->mutex); pthread_mutex_lock(&aq->mutex);
void * value = aq->buf[aq->readPos % aq->num]; void * value = aq->buf[aq->readPos % aq->num];
if(value) if (value) {
{
aq->buf[aq->readPos % aq->num] = NULL; aq->buf[aq->readPos % aq->num] = NULL;
aq->readPos = (aq->readPos + 1) % aq->num; aq->readPos = (aq->readPos + 1) % aq->num;
} }
@ -78,7 +79,8 @@ void* array_queue_pop(array_queue * aq)
return value; return value;
} }
size_t array_queue_get_size(array_queue * aq) size_t
array_queue_get_size(array_queue * aq)
{ {
pthread_mutex_lock(&aq->mutex); pthread_mutex_lock(&aq->mutex);
ssize_t r = aq->writePos - aq->readPos; ssize_t r = aq->writePos - aq->readPos;
@ -94,4 +96,4 @@ size_t array_queue_get_size(array_queue * aq)
} }
#endif #endif
#endif //CUBE_ARRAY_QUEUE_H #endif // CUBE_ARRAY_QUEUE_H

View file

@ -16,7 +16,8 @@
* export a function or macro called XASSERT that aborts the program. * export a function or macro called XASSERT that aborts the program.
*/ */
#define XASSERT(expr) do { \ #define XASSERT(expr) \
do { \
if (!(expr)) { \ if (!(expr)) { \
fprintf(stderr, "%s:%d - fatal error: %s\n", __FILE__, __LINE__, #expr); \ fprintf(stderr, "%s:%d - fatal error: %s\n", __FILE__, __LINE__, #expr); \
abort(); \ abort(); \

View file

@ -8,20 +8,21 @@
#if !defined(NDEBUG) #if !defined(NDEBUG)
#define NDEBUG #define NDEBUG
#endif #endif
#include <android/log.h>
#include <assert.h> #include <assert.h>
#include <dlfcn.h>
#include <pthread.h> #include <pthread.h>
#include <stdlib.h> #include <stdlib.h>
#include <time.h> #include <time.h>
#include <dlfcn.h>
#include <android/log.h>
#include "cubeb/cubeb.h"
#include "cubeb-internal.h"
#include "android/audiotrack_definitions.h" #include "android/audiotrack_definitions.h"
#include "cubeb-internal.h"
#include "cubeb/cubeb.h"
#ifndef ALOG #ifndef ALOG
#if defined(DEBUG) || defined(FORCE_ALOG) #if defined(DEBUG) || defined(FORCE_ALOG)
#define ALOG(args...) __android_log_print(ANDROID_LOG_INFO, "Gecko - Cubeb" , ## args) #define ALOG(args...) \
__android_log_print(ANDROID_LOG_INFO, "Gecko - Cubeb", ##args)
#else #else
#define ALOG(args...) #define ALOG(args...)
#endif #endif
@ -35,37 +36,44 @@
* call dlsym to get the symbol |mangled_name|, handle the error and store the * call dlsym to get the symbol |mangled_name|, handle the error and store the
* pointer in |pointer|. Because depending on Android version, we want different * pointer in |pointer|. Because depending on Android version, we want different
* symbols, not finding a symbol is not an error. */ * symbols, not finding a symbol is not an error. */
#define DLSYM_DLERROR(mangled_name, pointer, lib) \ #define DLSYM_DLERROR(mangled_name, pointer, lib) \
do { \ do { \
pointer = dlsym(lib, mangled_name); \ pointer = dlsym(lib, mangled_name); \
if (!pointer) { \ if (!pointer) { \
ALOG("error while loading %stm: %stm\n", mangled_name, dlerror()); \ ALOG("error while loading %stm: %stm\n", mangled_name, dlerror()); \
} else { \ } else { \
ALOG("%stm: OK", mangled_name); \ ALOG("%stm: OK", mangled_name); \
} \ } \
} while(0); } while (0);
static struct cubeb_ops const audiotrack_ops; static struct cubeb_ops const audiotrack_ops;
void audiotrack_destroy(cubeb * context); void
void audiotrack_stream_destroy(cubeb_stream * stream); audiotrack_destroy(cubeb * context);
void
audiotrack_stream_destroy(cubeb_stream * stream);
struct AudioTrack { struct AudioTrack {
/* only available on ICS and later. The second int paramter is in fact of type audio_stream_type_t. */ /* only available on ICS and later. The second int paramter is in fact of type
/* static */ status_t (*get_min_frame_count)(int* frame_count, int stream_type, uint32_t rate); * audio_stream_type_t. */
/* static */ status_t (*get_min_frame_count)(int * frame_count,
int stream_type, uint32_t rate);
/* if we have a recent ctor, but can't find the above symbol, we /* if we have a recent ctor, but can't find the above symbol, we
* can get the minimum frame count with this signature, and we are * can get the minimum frame count with this signature, and we are
* running gingerbread. */ * running gingerbread. */
/* static */ status_t (*get_min_frame_count_gingerbread)(int* frame_count, int stream_type, uint32_t rate); /* static */ status_t (*get_min_frame_count_gingerbread)(int * frame_count,
void* (*ctor)(void* instance, int, unsigned int, int, int, int, unsigned int, void (*)(int, void*, void*), void*, int, int); int stream_type,
void* (*dtor)(void* instance); uint32_t rate);
void (*start)(void* instance); void * (*ctor)(void * instance, int, unsigned int, int, int, int,
void (*pause)(void* instance); unsigned int, void (*)(int, void *, void *), void *, int, int);
uint32_t (*latency)(void* instance); void * (*dtor)(void * instance);
status_t (*check)(void* instance); void (*start)(void * instance);
status_t (*get_position)(void* instance, uint32_t* position); void (*pause)(void * instance);
/* static */ int (*get_output_samplingrate)(int* samplerate, int stream); uint32_t (*latency)(void * instance);
status_t (*set_marker_position)(void* instance, unsigned int); status_t (*check)(void * instance);
status_t (*set_volume)(void* instance, float left, float right); status_t (*get_position)(void * instance, uint32_t * position);
/* static */ int (*get_output_samplingrate)(int * samplerate, int stream);
status_t (*set_marker_position)(void * instance, unsigned int);
status_t (*set_volume)(void * instance, float left, float right);
}; };
struct cubeb { struct cubeb {
@ -89,19 +97,20 @@ struct cubeb_stream {
}; };
static void static void
audiotrack_refill(int event, void* user, void* info) audiotrack_refill(int event, void * user, void * info)
{ {
cubeb_stream * stream = user; cubeb_stream * stream = user;
switch (event) { switch (event) {
case EVENT_MORE_DATA: { case EVENT_MORE_DATA: {
long got = 0; long got = 0;
struct Buffer * b = (struct Buffer*)info; struct Buffer * b = (struct Buffer *)info;
if (stream->draining) { if (stream->draining) {
return; return;
} }
got = stream->data_callback(stream, stream->user_ptr, NULL, b->raw, b->frameCount); got = stream->data_callback(stream, stream->user_ptr, NULL, b->raw,
b->frameCount);
stream->written += got; stream->written += got;
@ -109,7 +118,8 @@ audiotrack_refill(int event, void* user, void* info)
stream->draining = 1; stream->draining = 1;
/* set a marker so we are notified when the are done draining, that is, /* set a marker so we are notified when the are done draining, that is,
* when every frame has been played by android. */ * when every frame has been played by android. */
stream->context->klass.set_marker_position(stream->instance, stream->written); stream->context->klass.set_marker_position(stream->instance,
stream->written);
} }
break; break;
@ -125,7 +135,9 @@ audiotrack_refill(int event, void* user, void* info)
stream->state_callback(stream, stream->user_ptr, CUBEB_STATE_DRAINED); stream->state_callback(stream, stream->user_ptr, CUBEB_STATE_DRAINED);
break; break;
case EVENT_NEW_POS: case EVENT_NEW_POS:
assert(0 && "We don't support the setPositionUpdatePeriod feature of audiotrack."); assert(
0 &&
"We don't support the setPositionUpdatePeriod feature of audiotrack.");
break; break;
case EVENT_BUFFER_END: case EVENT_BUFFER_END:
assert(0 && "Should not happen."); assert(0 && "Should not happen.");
@ -142,14 +154,17 @@ audiotrack_version_is_gingerbread(cubeb * ctx)
} }
int int
audiotrack_get_min_frame_count(cubeb * ctx, cubeb_stream_params * params, int * min_frame_count) audiotrack_get_min_frame_count(cubeb * ctx, cubeb_stream_params * params,
int * min_frame_count)
{ {
status_t status; status_t status;
/* Recent Android have a getMinFrameCount method. */ /* Recent Android have a getMinFrameCount method. */
if (!audiotrack_version_is_gingerbread(ctx)) { if (!audiotrack_version_is_gingerbread(ctx)) {
status = ctx->klass.get_min_frame_count(min_frame_count, AUDIO_STREAM_TYPE_MUSIC, params->rate); status = ctx->klass.get_min_frame_count(
min_frame_count, AUDIO_STREAM_TYPE_MUSIC, params->rate);
} else { } else {
status = ctx->klass.get_min_frame_count_gingerbread(min_frame_count, AUDIO_STREAM_TYPE_MUSIC, params->rate); status = ctx->klass.get_min_frame_count_gingerbread(
min_frame_count, AUDIO_STREAM_TYPE_MUSIC, params->rate);
} }
if (status != 0) { if (status != 0) {
ALOG("error getting the min frame count"); ALOG("error getting the min frame count");
@ -162,7 +177,7 @@ int
audiotrack_init(cubeb ** context, char const * context_name) audiotrack_init(cubeb ** context, char const * context_name)
{ {
cubeb * ctx; cubeb * ctx;
struct AudioTrack* c; struct AudioTrack * c;
assert(context); assert(context);
*context = NULL; *context = NULL;
@ -182,34 +197,45 @@ audiotrack_init(cubeb ** context, char const * context_name)
} }
/* Recent Android first, then Gingerbread. */ /* Recent Android first, then Gingerbread. */
DLSYM_DLERROR("_ZN7android10AudioTrackC1EijiiijPFviPvS1_ES1_ii", ctx->klass.ctor, ctx->library); DLSYM_DLERROR("_ZN7android10AudioTrackC1EijiiijPFviPvS1_ES1_ii",
ctx->klass.ctor, ctx->library);
DLSYM_DLERROR("_ZN7android10AudioTrackD1Ev", ctx->klass.dtor, ctx->library); DLSYM_DLERROR("_ZN7android10AudioTrackD1Ev", ctx->klass.dtor, ctx->library);
DLSYM_DLERROR("_ZNK7android10AudioTrack7latencyEv", ctx->klass.latency, ctx->library); DLSYM_DLERROR("_ZNK7android10AudioTrack7latencyEv", ctx->klass.latency,
DLSYM_DLERROR("_ZNK7android10AudioTrack9initCheckEv", ctx->klass.check, ctx->library); ctx->library);
DLSYM_DLERROR("_ZNK7android10AudioTrack9initCheckEv", ctx->klass.check,
ctx->library);
DLSYM_DLERROR("_ZN7android11AudioSystem21getOutputSamplingRateEPii", ctx->klass.get_output_samplingrate, ctx->library); DLSYM_DLERROR("_ZN7android11AudioSystem21getOutputSamplingRateEPii",
ctx->klass.get_output_samplingrate, ctx->library);
/* |getMinFrameCount| is available on gingerbread and ICS with different signatures. */ /* |getMinFrameCount| is available on gingerbread and ICS with different
DLSYM_DLERROR("_ZN7android10AudioTrack16getMinFrameCountEPi19audio_stream_type_tj", ctx->klass.get_min_frame_count, ctx->library); * signatures. */
DLSYM_DLERROR(
"_ZN7android10AudioTrack16getMinFrameCountEPi19audio_stream_type_tj",
ctx->klass.get_min_frame_count, ctx->library);
if (!ctx->klass.get_min_frame_count) { if (!ctx->klass.get_min_frame_count) {
DLSYM_DLERROR("_ZN7android10AudioTrack16getMinFrameCountEPiij", ctx->klass.get_min_frame_count_gingerbread, ctx->library); DLSYM_DLERROR("_ZN7android10AudioTrack16getMinFrameCountEPiij",
ctx->klass.get_min_frame_count_gingerbread, ctx->library);
} }
DLSYM_DLERROR("_ZN7android10AudioTrack5startEv", ctx->klass.start, ctx->library); DLSYM_DLERROR("_ZN7android10AudioTrack5startEv", ctx->klass.start,
DLSYM_DLERROR("_ZN7android10AudioTrack5pauseEv", ctx->klass.pause, ctx->library); ctx->library);
DLSYM_DLERROR("_ZN7android10AudioTrack11getPositionEPj", ctx->klass.get_position, ctx->library); DLSYM_DLERROR("_ZN7android10AudioTrack5pauseEv", ctx->klass.pause,
DLSYM_DLERROR("_ZN7android10AudioTrack17setMarkerPositionEj", ctx->klass.set_marker_position, ctx->library); ctx->library);
DLSYM_DLERROR("_ZN7android10AudioTrack9setVolumeEff", ctx->klass.set_volume, ctx->library); DLSYM_DLERROR("_ZN7android10AudioTrack11getPositionEPj",
ctx->klass.get_position, ctx->library);
DLSYM_DLERROR("_ZN7android10AudioTrack17setMarkerPositionEj",
ctx->klass.set_marker_position, ctx->library);
DLSYM_DLERROR("_ZN7android10AudioTrack9setVolumeEff", ctx->klass.set_volume,
ctx->library);
/* check that we have a combination of symbol that makes sense */ /* check that we have a combination of symbol that makes sense */
c = &ctx->klass; c = &ctx->klass;
if(!(c->ctor && if (!(c->ctor && c->dtor && c->latency && c->check &&
c->dtor && c->latency && c->check && /* at least one way to get the minimum frame count to request. */
/* at least one way to get the minimum frame count to request. */ (c->get_min_frame_count || c->get_min_frame_count_gingerbread) &&
(c->get_min_frame_count || c->start && c->pause && c->get_position && c->set_marker_position)) {
c->get_min_frame_count_gingerbread) &&
c->start && c->pause && c->get_position && c->set_marker_position)) {
ALOG("Could not find all the symbols we need."); ALOG("Could not find all the symbols we need.");
audiotrack_destroy(ctx); audiotrack_destroy(ctx);
return CUBEB_ERROR; return CUBEB_ERROR;
@ -234,14 +260,16 @@ audiotrack_get_max_channel_count(cubeb * ctx, uint32_t * max_channels)
assert(ctx && max_channels); assert(ctx && max_channels);
/* The android mixer handles up to two channels, see /* The android mixer handles up to two channels, see
http://androidxref.com/4.2.2_r1/xref/frameworks/av/services/audioflinger/AudioFlinger.h#67 */ http://androidxref.com/4.2.2_r1/xref/frameworks/av/services/audioflinger/AudioFlinger.h#67
*/
*max_channels = 2; *max_channels = 2;
return CUBEB_OK; return CUBEB_OK;
} }
static int static int
audiotrack_get_min_latency(cubeb * ctx, cubeb_stream_params params, uint32_t * latency_ms) audiotrack_get_min_latency(cubeb * ctx, cubeb_stream_params params,
uint32_t * latency_ms)
{ {
/* We always use the lowest latency possible when using this backend (see /* We always use the lowest latency possible when using this backend (see
* audiotrack_stream_init), so this value is not going to be used. */ * audiotrack_stream_init), so this value is not going to be used. */
@ -276,15 +304,13 @@ audiotrack_destroy(cubeb * context)
} }
int int
audiotrack_stream_init(cubeb * ctx, cubeb_stream ** stream, char const * stream_name, audiotrack_stream_init(cubeb * ctx, cubeb_stream ** stream,
cubeb_devid input_device, char const * stream_name, cubeb_devid input_device,
cubeb_stream_params * input_stream_params, cubeb_stream_params * input_stream_params,
cubeb_devid output_device, cubeb_devid output_device,
cubeb_stream_params * output_stream_params, cubeb_stream_params * output_stream_params,
unsigned int latency, unsigned int latency, cubeb_data_callback data_callback,
cubeb_data_callback data_callback, cubeb_state_callback state_callback, void * user_ptr)
cubeb_state_callback state_callback,
void * user_ptr)
{ {
cubeb_stream * stm; cubeb_stream * stm;
int32_t channels; int32_t channels;
@ -303,7 +329,8 @@ audiotrack_stream_init(cubeb * ctx, cubeb_stream ** stream, char const * stream_
return CUBEB_ERROR_INVALID_FORMAT; return CUBEB_ERROR_INVALID_FORMAT;
} }
if (audiotrack_get_min_frame_count(ctx, output_stream_params, (int *)&min_frame_count)) { if (audiotrack_get_min_frame_count(ctx, output_stream_params,
(int *)&min_frame_count)) {
return CUBEB_ERROR; return CUBEB_ERROR;
} }
@ -317,21 +344,25 @@ audiotrack_stream_init(cubeb * ctx, cubeb_stream ** stream, char const * stream_
stm->params = *output_stream_params; stm->params = *output_stream_params;
stm->instance = calloc(SIZE_AUDIOTRACK_INSTANCE, 1); stm->instance = calloc(SIZE_AUDIOTRACK_INSTANCE, 1);
(*(uint32_t*)((intptr_t)stm->instance + SIZE_AUDIOTRACK_INSTANCE - 4)) = 0xbaadbaad; (*(uint32_t *)((intptr_t)stm->instance + SIZE_AUDIOTRACK_INSTANCE - 4)) =
0xbaadbaad;
assert(stm->instance && "cubeb: EOM"); assert(stm->instance && "cubeb: EOM");
/* gingerbread uses old channel layout enum */ /* gingerbread uses old channel layout enum */
if (audiotrack_version_is_gingerbread(ctx)) { if (audiotrack_version_is_gingerbread(ctx)) {
channels = stm->params.channels == 2 ? AUDIO_CHANNEL_OUT_STEREO_Legacy : AUDIO_CHANNEL_OUT_MONO_Legacy; channels = stm->params.channels == 2 ? AUDIO_CHANNEL_OUT_STEREO_Legacy
: AUDIO_CHANNEL_OUT_MONO_Legacy;
} else { } else {
channels = stm->params.channels == 2 ? AUDIO_CHANNEL_OUT_STEREO_ICS : AUDIO_CHANNEL_OUT_MONO_ICS; channels = stm->params.channels == 2 ? AUDIO_CHANNEL_OUT_STEREO_ICS
: AUDIO_CHANNEL_OUT_MONO_ICS;
} }
ctx->klass.ctor(stm->instance, AUDIO_STREAM_TYPE_MUSIC, stm->params.rate, ctx->klass.ctor(stm->instance, AUDIO_STREAM_TYPE_MUSIC, stm->params.rate,
AUDIO_FORMAT_PCM_16_BIT, channels, min_frame_count, 0, AUDIO_FORMAT_PCM_16_BIT, channels, min_frame_count, 0,
audiotrack_refill, stm, 0, 0); audiotrack_refill, stm, 0, 0);
assert((*(uint32_t*)((intptr_t)stm->instance + SIZE_AUDIOTRACK_INSTANCE - 4)) == 0xbaadbaad); assert((*(uint32_t *)((intptr_t)stm->instance + SIZE_AUDIOTRACK_INSTANCE -
4)) == 0xbaadbaad);
if (ctx->klass.check(stm->instance)) { if (ctx->klass.check(stm->instance)) {
ALOG("stream not initialized properly."); ALOG("stream not initialized properly.");
@ -418,26 +449,24 @@ audiotrack_stream_set_volume(cubeb_stream * stream, float volume)
} }
static struct cubeb_ops const audiotrack_ops = { static struct cubeb_ops const audiotrack_ops = {
.init = audiotrack_init, .init = audiotrack_init,
.get_backend_id = audiotrack_get_backend_id, .get_backend_id = audiotrack_get_backend_id,
.get_max_channel_count = audiotrack_get_max_channel_count, .get_max_channel_count = audiotrack_get_max_channel_count,
.get_min_latency = audiotrack_get_min_latency, .get_min_latency = audiotrack_get_min_latency,
.get_preferred_sample_rate = audiotrack_get_preferred_sample_rate, .get_preferred_sample_rate = audiotrack_get_preferred_sample_rate,
.enumerate_devices = NULL, .enumerate_devices = NULL,
.device_collection_destroy = NULL, .device_collection_destroy = NULL,
.destroy = audiotrack_destroy, .destroy = audiotrack_destroy,
.stream_init = audiotrack_stream_init, .stream_init = audiotrack_stream_init,
.stream_destroy = audiotrack_stream_destroy, .stream_destroy = audiotrack_stream_destroy,
.stream_start = audiotrack_stream_start, .stream_start = audiotrack_stream_start,
.stream_stop = audiotrack_stream_stop, .stream_stop = audiotrack_stream_stop,
.stream_reset_default_device = NULL, .stream_get_position = audiotrack_stream_get_position,
.stream_get_position = audiotrack_stream_get_position, .stream_get_latency = audiotrack_stream_get_latency,
.stream_get_latency = audiotrack_stream_get_latency, .stream_get_input_latency = NULL,
.stream_get_input_latency = NULL, .stream_set_volume = audiotrack_stream_set_volume,
.stream_set_volume = audiotrack_stream_set_volume, .stream_set_name = NULL,
.stream_set_name = NULL, .stream_get_current_device = NULL,
.stream_get_current_device = NULL, .stream_device_destroy = NULL,
.stream_device_destroy = NULL, .stream_register_device_changed_callback = NULL,
.stream_register_device_changed_callback = NULL, .register_device_collection_changed = NULL};
.register_device_collection_changed = NULL
};

File diff suppressed because it is too large Load diff

View file

@ -11,50 +11,56 @@
#ifndef __FreeBSD__ #ifndef __FreeBSD__
#define _POSIX_SOURCE #define _POSIX_SOURCE
#endif #endif
#include <dlfcn.h>
#include <stdio.h>
#include <string.h>
#include <limits.h>
#include <stdlib.h>
#include <pthread.h>
#include <math.h>
#include "cubeb/cubeb.h"
#include "cubeb-internal.h" #include "cubeb-internal.h"
#include "cubeb/cubeb.h"
#include "cubeb_resampler.h" #include "cubeb_resampler.h"
#include "cubeb_utils.h" #include "cubeb_utils.h"
#include <dlfcn.h>
#include <limits.h>
#include <math.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <jack/jack.h> #include <jack/jack.h>
#include <jack/statistics.h> #include <jack/statistics.h>
#define JACK_API_VISIT(X) \ #ifdef DISABLE_LIBJACK_DLOPEN
X(jack_activate) \ #define WRAP(x) x
X(jack_client_close) \ #else
X(jack_client_open) \ #define WRAP(x) (*api_##x)
X(jack_connect) \ #define JACK_API_VISIT(X) \
X(jack_free) \ X(jack_activate) \
X(jack_get_ports) \ X(jack_client_close) \
X(jack_get_sample_rate) \ X(jack_client_open) \
X(jack_get_xrun_delayed_usecs) \ X(jack_connect) \
X(jack_get_buffer_size) \ X(jack_free) \
X(jack_port_get_buffer) \ X(jack_get_ports) \
X(jack_port_name) \ X(jack_get_sample_rate) \
X(jack_port_register) \ X(jack_get_xrun_delayed_usecs) \
X(jack_port_unregister) \ X(jack_get_buffer_size) \
X(jack_port_get_latency_range) \ X(jack_port_get_buffer) \
X(jack_set_process_callback) \ X(jack_port_name) \
X(jack_set_xrun_callback) \ X(jack_port_register) \
X(jack_set_graph_order_callback) \ X(jack_port_unregister) \
X(jack_set_error_function) \ X(jack_port_get_latency_range) \
X(jack_set_process_callback) \
X(jack_set_xrun_callback) \
X(jack_set_graph_order_callback) \
X(jack_set_error_function) \
X(jack_set_info_function) X(jack_set_info_function)
#define IMPORT_FUNC(x) static decltype(x) * api_##x; #define IMPORT_FUNC(x) static decltype(x) * api_##x;
JACK_API_VISIT(IMPORT_FUNC); JACK_API_VISIT(IMPORT_FUNC);
#undef IMPORT_FUNC
#endif
#define JACK_DEFAULT_IN "JACK capture" #define JACK_DEFAULT_IN "JACK capture"
#define JACK_DEFAULT_OUT "JACK playback" #define JACK_DEFAULT_OUT "JACK playback"
static const int MAX_STREAMS = 16; static const int MAX_STREAMS = 16;
static const int MAX_CHANNELS = 8; static const int MAX_CHANNELS = 8;
static const int FIFO_SIZE = 4096 * sizeof(float); static const int FIFO_SIZE = 4096 * sizeof(float);
enum devstream { enum devstream {
@ -64,6 +70,12 @@ enum devstream {
DUPLEX, DUPLEX,
}; };
enum cbjack_connect_ports_options {
CBJACK_CP_OPTIONS_NONE = 0x0,
CBJACK_CP_OPTIONS_SKIP_OUTPUT = 0x1,
CBJACK_CP_OPTIONS_SKIP_INPUT = 0x2,
};
static void static void
s16ne_to_float(float * dst, const int16_t * src, size_t n) s16ne_to_float(float * dst, const int16_t * src, size_t n)
{ {
@ -75,71 +87,95 @@ static void
float_to_s16ne(int16_t * dst, float * src, size_t n) float_to_s16ne(int16_t * dst, float * src, size_t n)
{ {
for (size_t i = 0; i < n; i++) { for (size_t i = 0; i < n; i++) {
if (*src > 1.f) *src = 1.f; if (*src > 1.f)
if (*src < -1.f) *src = -1.f; *src = 1.f;
if (*src < -1.f)
*src = -1.f;
*(dst++) = (int16_t)((int16_t)(*(src++) * 32767)); *(dst++) = (int16_t)((int16_t)(*(src++) * 32767));
} }
} }
extern "C" extern "C" {
{ /*static*/ int
/*static*/ int jack_init (cubeb ** context, char const * context_name); jack_init(cubeb ** context, char const * context_name);
} }
static char const * cbjack_get_backend_id(cubeb * context); static char const *
static int cbjack_get_max_channel_count(cubeb * ctx, uint32_t * max_channels); cbjack_get_backend_id(cubeb * context);
static int cbjack_get_min_latency(cubeb * ctx, cubeb_stream_params params, uint32_t * latency_frames); static int
static int cbjack_get_latency(cubeb_stream * stm, unsigned int * latency_frames); cbjack_get_max_channel_count(cubeb * ctx, uint32_t * max_channels);
static int cbjack_get_preferred_sample_rate(cubeb * ctx, uint32_t * rate); static int
static void cbjack_destroy(cubeb * context); cbjack_get_min_latency(cubeb * ctx, cubeb_stream_params params,
static void cbjack_interleave_capture(cubeb_stream * stream, float **in, jack_nframes_t nframes, bool format_mismatch); uint32_t * latency_frames);
static void cbjack_deinterleave_playback_refill_s16ne(cubeb_stream * stream, short **bufs_in, float **bufs_out, jack_nframes_t nframes); static int
static void cbjack_deinterleave_playback_refill_float(cubeb_stream * stream, float **bufs_in, float **bufs_out, jack_nframes_t nframes); cbjack_get_latency(cubeb_stream * stm, unsigned int * latency_frames);
static int cbjack_stream_device_destroy(cubeb_stream * stream, static int
cubeb_device * device); cbjack_get_preferred_sample_rate(cubeb * ctx, uint32_t * rate);
static int cbjack_stream_get_current_device(cubeb_stream * stm, cubeb_device ** const device); static void
static int cbjack_enumerate_devices(cubeb * context, cubeb_device_type type, cbjack_destroy(cubeb * context);
cubeb_device_collection * collection); static void
static int cbjack_device_collection_destroy(cubeb * context, cbjack_interleave_capture(cubeb_stream * stream, float ** in,
cubeb_device_collection * collection); jack_nframes_t nframes, bool format_mismatch);
static int cbjack_stream_init(cubeb * context, cubeb_stream ** stream, char const * stream_name, static void
cubeb_devid input_device, cbjack_deinterleave_playback_refill_s16ne(cubeb_stream * stream,
cubeb_stream_params * input_stream_params, short ** bufs_in, float ** bufs_out,
cubeb_devid output_device, jack_nframes_t nframes);
cubeb_stream_params * output_stream_params, static void
unsigned int latency_frames, cbjack_deinterleave_playback_refill_float(cubeb_stream * stream,
cubeb_data_callback data_callback, float ** bufs_in, float ** bufs_out,
cubeb_state_callback state_callback, jack_nframes_t nframes);
void * user_ptr); static int
static void cbjack_stream_destroy(cubeb_stream * stream); cbjack_stream_device_destroy(cubeb_stream * stream, cubeb_device * device);
static int cbjack_stream_start(cubeb_stream * stream); static int
static int cbjack_stream_stop(cubeb_stream * stream); cbjack_stream_get_current_device(cubeb_stream * stm,
static int cbjack_stream_get_position(cubeb_stream * stream, uint64_t * position); cubeb_device ** const device);
static int cbjack_stream_set_volume(cubeb_stream * stm, float volume); static int
cbjack_enumerate_devices(cubeb * context, cubeb_device_type type,
cubeb_device_collection * collection);
static int
cbjack_device_collection_destroy(cubeb * context,
cubeb_device_collection * collection);
static int
cbjack_stream_init(cubeb * context, cubeb_stream ** stream,
char const * stream_name, cubeb_devid input_device,
cubeb_stream_params * input_stream_params,
cubeb_devid output_device,
cubeb_stream_params * output_stream_params,
unsigned int latency_frames,
cubeb_data_callback data_callback,
cubeb_state_callback state_callback, void * user_ptr);
static void
cbjack_stream_destroy(cubeb_stream * stream);
static int
cbjack_stream_start(cubeb_stream * stream);
static int
cbjack_stream_stop(cubeb_stream * stream);
static int
cbjack_stream_get_position(cubeb_stream * stream, uint64_t * position);
static int
cbjack_stream_set_volume(cubeb_stream * stm, float volume);
static struct cubeb_ops const cbjack_ops = { static struct cubeb_ops const cbjack_ops = {
.init = jack_init, .init = jack_init,
.get_backend_id = cbjack_get_backend_id, .get_backend_id = cbjack_get_backend_id,
.get_max_channel_count = cbjack_get_max_channel_count, .get_max_channel_count = cbjack_get_max_channel_count,
.get_min_latency = cbjack_get_min_latency, .get_min_latency = cbjack_get_min_latency,
.get_preferred_sample_rate = cbjack_get_preferred_sample_rate, .get_preferred_sample_rate = cbjack_get_preferred_sample_rate,
.enumerate_devices = cbjack_enumerate_devices, .enumerate_devices = cbjack_enumerate_devices,
.device_collection_destroy = cbjack_device_collection_destroy, .device_collection_destroy = cbjack_device_collection_destroy,
.destroy = cbjack_destroy, .destroy = cbjack_destroy,
.stream_init = cbjack_stream_init, .stream_init = cbjack_stream_init,
.stream_destroy = cbjack_stream_destroy, .stream_destroy = cbjack_stream_destroy,
.stream_start = cbjack_stream_start, .stream_start = cbjack_stream_start,
.stream_stop = cbjack_stream_stop, .stream_stop = cbjack_stream_stop,
.stream_reset_default_device = NULL, .stream_get_position = cbjack_stream_get_position,
.stream_get_position = cbjack_stream_get_position, .stream_get_latency = cbjack_get_latency,
.stream_get_latency = cbjack_get_latency, .stream_get_input_latency = NULL,
.stream_get_input_latency = NULL, .stream_set_volume = cbjack_stream_set_volume,
.stream_set_volume = cbjack_stream_set_volume, .stream_set_name = NULL,
.stream_set_name = NULL, .stream_get_current_device = cbjack_stream_get_current_device,
.stream_get_current_device = cbjack_stream_get_current_device, .stream_device_destroy = cbjack_stream_device_destroy,
.stream_device_destroy = cbjack_stream_device_destroy, .stream_register_device_changed_callback = NULL,
.stream_register_device_changed_callback = NULL, .register_device_collection_changed = NULL};
.register_device_collection_changed = NULL
};
struct cubeb_stream { struct cubeb_stream {
/* Note: Must match cubeb_stream layout in cubeb.c. */ /* Note: Must match cubeb_stream layout in cubeb.c. */
@ -150,7 +186,7 @@ struct cubeb_stream {
/**< Mutex for each stream */ /**< Mutex for each stream */
pthread_mutex_t mutex; pthread_mutex_t mutex;
bool in_use; /**< Set to false iff the stream is free */ bool in_use; /**< Set to false iff the stream is free */
bool ports_ready; /**< Set to true iff the JACK ports are ready */ bool ports_ready; /**< Set to true iff the JACK ports are ready */
cubeb_data_callback data_callback; cubeb_data_callback data_callback;
@ -205,15 +241,16 @@ struct cubeb {
static int static int
load_jack_lib(cubeb * context) load_jack_lib(cubeb * context)
{ {
#ifndef DISABLE_LIBJACK_DLOPEN
#ifdef __APPLE__ #ifdef __APPLE__
context->libjack = dlopen("libjack.0.dylib", RTLD_LAZY); context->libjack = dlopen("libjack.0.dylib", RTLD_LAZY);
context->libjack = dlopen("/usr/local/lib/libjack.0.dylib", RTLD_LAZY); context->libjack = dlopen("/usr/local/lib/libjack.0.dylib", RTLD_LAZY);
#elif defined(__WIN32__) #elif defined(__WIN32__)
# ifdef _WIN64 #ifdef _WIN64
context->libjack = LoadLibrary("libjack64.dll"); context->libjack = LoadLibrary("libjack64.dll");
# else #else
context->libjack = LoadLibrary("libjack.dll"); context->libjack = LoadLibrary("libjack.dll");
# endif #endif
#else #else
context->libjack = dlopen("libjack.so.0", RTLD_LAZY); context->libjack = dlopen("libjack.so.0", RTLD_LAZY);
if (!context->libjack) { if (!context->libjack) {
@ -224,56 +261,59 @@ load_jack_lib(cubeb * context)
return CUBEB_ERROR; return CUBEB_ERROR;
} }
#define LOAD(x) \ #define LOAD(x) \
{ \ { \
api_##x = (decltype(x)*)dlsym(context->libjack, #x); \ api_##x = (decltype(x) *)dlsym(context->libjack, #x); \
if (!api_##x) { \ if (!api_##x) { \
dlclose(context->libjack); \ dlclose(context->libjack); \
return CUBEB_ERROR; \ return CUBEB_ERROR; \
} \ } \
} }
JACK_API_VISIT(LOAD); JACK_API_VISIT(LOAD);
#undef LOAD #undef LOAD
#endif
return CUBEB_OK; return CUBEB_OK;
} }
static void static void
cbjack_connect_port_out (cubeb_stream * stream, const size_t out_port, const char * const phys_in_port) cbjack_connect_port_out(cubeb_stream * stream, const size_t out_port,
const char * const phys_in_port)
{ {
const char *src_port = api_jack_port_name (stream->output_ports[out_port]); const char * src_port = WRAP(jack_port_name)(stream->output_ports[out_port]);
api_jack_connect (stream->context->jack_client, src_port, phys_in_port); WRAP(jack_connect)(stream->context->jack_client, src_port, phys_in_port);
} }
static void static void
cbjack_connect_port_in (cubeb_stream * stream, const char * const phys_out_port, size_t in_port) cbjack_connect_port_in(cubeb_stream * stream, const char * const phys_out_port,
size_t in_port)
{ {
const char *src_port = api_jack_port_name (stream->input_ports[in_port]); const char * src_port = WRAP(jack_port_name)(stream->input_ports[in_port]);
api_jack_connect (stream->context->jack_client, phys_out_port, src_port); WRAP(jack_connect)(stream->context->jack_client, phys_out_port, src_port);
} }
static int static int
cbjack_connect_ports (cubeb_stream * stream) cbjack_connect_ports(cubeb_stream * stream,
enum cbjack_connect_ports_options options)
{ {
int r = CUBEB_ERROR; int r = CUBEB_ERROR;
const char ** phys_in_ports = api_jack_get_ports (stream->context->jack_client, const char ** phys_in_ports =
NULL, NULL, WRAP(jack_get_ports)(stream->context->jack_client, NULL, NULL,
JackPortIsInput JackPortIsInput | JackPortIsPhysical);
| JackPortIsPhysical); const char ** phys_out_ports =
const char ** phys_out_ports = api_jack_get_ports (stream->context->jack_client, WRAP(jack_get_ports)(stream->context->jack_client, NULL, NULL,
NULL, NULL, JackPortIsOutput | JackPortIsPhysical);
JackPortIsOutput
| JackPortIsPhysical);
if (phys_in_ports == NULL || *phys_in_ports == NULL) { if (phys_in_ports == NULL || *phys_in_ports == NULL ||
options & CBJACK_CP_OPTIONS_SKIP_OUTPUT) {
goto skipplayback; goto skipplayback;
} }
// Connect outputs to playback // Connect outputs to playback
for (unsigned int c = 0; c < stream->out_params.channels && phys_in_ports[c] != NULL; c++) { for (unsigned int c = 0;
c < stream->out_params.channels && phys_in_ports[c] != NULL; c++) {
cbjack_connect_port_out(stream, c, phys_in_ports[c]); cbjack_connect_port_out(stream, c, phys_in_ports[c]);
} }
@ -285,20 +325,22 @@ cbjack_connect_ports (cubeb_stream * stream)
r = CUBEB_OK; r = CUBEB_OK;
skipplayback: skipplayback:
if (phys_out_ports == NULL || *phys_out_ports == NULL) { if (phys_out_ports == NULL || *phys_out_ports == NULL ||
options & CBJACK_CP_OPTIONS_SKIP_INPUT) {
goto end; goto end;
} }
// Connect inputs to capture // Connect inputs to capture
for (unsigned int c = 0; c < stream->in_params.channels && phys_out_ports[c] != NULL; c++) { for (unsigned int c = 0;
c < stream->in_params.channels && phys_out_ports[c] != NULL; c++) {
cbjack_connect_port_in(stream, phys_out_ports[c], c); cbjack_connect_port_in(stream, phys_out_ports[c], c);
} }
r = CUBEB_OK; r = CUBEB_OK;
end: end:
if (phys_out_ports) { if (phys_out_ports) {
api_jack_free(phys_out_ports); WRAP(jack_free)(phys_out_ports);
} }
if (phys_in_ports) { if (phys_in_ports) {
api_jack_free(phys_in_ports); WRAP(jack_free)(phys_in_ports);
} }
return r; return r;
} }
@ -308,8 +350,9 @@ cbjack_xrun_callback(void * arg)
{ {
cubeb * ctx = (cubeb *)arg; cubeb * ctx = (cubeb *)arg;
float delay = api_jack_get_xrun_delayed_usecs(ctx->jack_client); float delay = WRAP(jack_get_xrun_delayed_usecs)(ctx->jack_client);
float fragments = ceilf(((delay / 1000000.0) * ctx->jack_sample_rate) / ctx->jack_buffer_size); float fragments = ceilf(((delay / 1000000.0) * ctx->jack_sample_rate) /
ctx->jack_buffer_size);
ctx->jack_xruns += (unsigned int)fragments; ctx->jack_xruns += (unsigned int)fragments;
return 0; return 0;
@ -324,7 +367,7 @@ cbjack_graph_order_callback(void * arg)
jack_nframes_t port_latency, max_latency = 0; jack_nframes_t port_latency, max_latency = 0;
for (int j = 0; j < MAX_STREAMS; j++) { for (int j = 0; j < MAX_STREAMS; j++) {
cubeb_stream *stm = &ctx->streams[j]; cubeb_stream * stm = &ctx->streams[j];
if (!stm->in_use) if (!stm->in_use)
continue; continue;
@ -332,10 +375,11 @@ cbjack_graph_order_callback(void * arg)
continue; continue;
for (i = 0; i < (int)stm->out_params.channels; ++i) { for (i = 0; i < (int)stm->out_params.channels; ++i) {
api_jack_port_get_latency_range(stm->output_ports[i], JackPlaybackLatency, &latency_range); WRAP(jack_port_get_latency_range)
(stm->output_ports[i], JackPlaybackLatency, &latency_range);
port_latency = latency_range.max; port_latency = latency_range.max;
if (port_latency > max_latency) if (port_latency > max_latency)
max_latency = port_latency; max_latency = port_latency;
} }
/* Cap minimum latency to 128 frames */ /* Cap minimum latency to 128 frames */
if (max_latency < 128) if (max_latency < 128)
@ -357,9 +401,9 @@ cbjack_process(jack_nframes_t nframes, void * arg)
ctx->jack_xruns = 0; ctx->jack_xruns = 0;
for (int j = 0; j < MAX_STREAMS; j++) { for (int j = 0; j < MAX_STREAMS; j++) {
cubeb_stream *stm = &ctx->streams[j]; cubeb_stream * stm = &ctx->streams[j];
float *bufs_out[stm->out_params.channels]; float * bufs_out[stm->out_params.channels];
float *bufs_in[stm->in_params.channels]; float * bufs_in[stm->in_params.channels];
if (!stm->in_use) if (!stm->in_use)
continue; continue;
@ -373,18 +417,20 @@ cbjack_process(jack_nframes_t nframes, void * arg)
if (stm->devs & OUT_ONLY) { if (stm->devs & OUT_ONLY) {
// get jack output buffers // get jack output buffers
for (i = 0; i < (int)stm->out_params.channels; i++) for (i = 0; i < (int)stm->out_params.channels; i++)
bufs_out[i] = (float*)api_jack_port_get_buffer(stm->output_ports[i], nframes); bufs_out[i] =
(float *)WRAP(jack_port_get_buffer)(stm->output_ports[i], nframes);
} }
if (stm->devs & IN_ONLY) { if (stm->devs & IN_ONLY) {
// get jack input buffers // get jack input buffers
for (i = 0; i < (int)stm->in_params.channels; i++) for (i = 0; i < (int)stm->in_params.channels; i++)
bufs_in[i] = (float*)api_jack_port_get_buffer(stm->input_ports[i], nframes); bufs_in[i] =
(float *)WRAP(jack_port_get_buffer)(stm->input_ports[i], nframes);
} }
if (stm->pause) { if (stm->pause) {
// paused, play silence on output // paused, play silence on output
if (stm->devs & OUT_ONLY) { if (stm->devs & OUT_ONLY) {
for (unsigned int c = 0; c < stm->out_params.channels; c++) { for (unsigned int c = 0; c < stm->out_params.channels; c++) {
float* buffer_out = bufs_out[c]; float * buffer_out = bufs_out[c];
for (long f = 0; f < nframes; f++) { for (long f = 0; f < nframes; f++) {
buffer_out[f] = 0.f; buffer_out[f] = 0.f;
} }
@ -393,7 +439,7 @@ cbjack_process(jack_nframes_t nframes, void * arg)
if (stm->devs & IN_ONLY) { if (stm->devs & IN_ONLY) {
// paused, capture silence // paused, capture silence
for (unsigned int c = 0; c < stm->in_params.channels; c++) { for (unsigned int c = 0; c < stm->in_params.channels; c++) {
float* buffer_in = bufs_in[c]; float * buffer_in = bufs_in[c];
for (long f = 0; f < nframes; f++) { for (long f = 0; f < nframes; f++) {
buffer_in[f] = 0.f; buffer_in[f] = 0.f;
} }
@ -404,31 +450,38 @@ cbjack_process(jack_nframes_t nframes, void * arg)
// try to lock stream mutex // try to lock stream mutex
if (pthread_mutex_trylock(&stm->mutex) == 0) { if (pthread_mutex_trylock(&stm->mutex) == 0) {
int16_t *in_s16ne = stm->context->in_resampled_interleaved_buffer_s16ne; int16_t * in_s16ne =
float *in_float = stm->context->in_resampled_interleaved_buffer_float; stm->context->in_resampled_interleaved_buffer_s16ne;
float * in_float = stm->context->in_resampled_interleaved_buffer_float;
// unpaused, play audio // unpaused, play audio
if (stm->devs == DUPLEX) { if (stm->devs == DUPLEX) {
if (stm->out_params.format == CUBEB_SAMPLE_S16NE) { if (stm->out_params.format == CUBEB_SAMPLE_S16NE) {
cbjack_interleave_capture(stm, bufs_in, nframes, true); cbjack_interleave_capture(stm, bufs_in, nframes, true);
cbjack_deinterleave_playback_refill_s16ne(stm, &in_s16ne, bufs_out, nframes); cbjack_deinterleave_playback_refill_s16ne(stm, &in_s16ne, bufs_out,
nframes);
} else if (stm->out_params.format == CUBEB_SAMPLE_FLOAT32NE) { } else if (stm->out_params.format == CUBEB_SAMPLE_FLOAT32NE) {
cbjack_interleave_capture(stm, bufs_in, nframes, false); cbjack_interleave_capture(stm, bufs_in, nframes, false);
cbjack_deinterleave_playback_refill_float(stm, &in_float, bufs_out, nframes); cbjack_deinterleave_playback_refill_float(stm, &in_float, bufs_out,
nframes);
} }
} else if (stm->devs == IN_ONLY) { } else if (stm->devs == IN_ONLY) {
if (stm->in_params.format == CUBEB_SAMPLE_S16NE) { if (stm->in_params.format == CUBEB_SAMPLE_S16NE) {
cbjack_interleave_capture(stm, bufs_in, nframes, true); cbjack_interleave_capture(stm, bufs_in, nframes, true);
cbjack_deinterleave_playback_refill_s16ne(stm, &in_s16ne, nullptr, nframes); cbjack_deinterleave_playback_refill_s16ne(stm, &in_s16ne, nullptr,
nframes);
} else if (stm->in_params.format == CUBEB_SAMPLE_FLOAT32NE) { } else if (stm->in_params.format == CUBEB_SAMPLE_FLOAT32NE) {
cbjack_interleave_capture(stm, bufs_in, nframes, false); cbjack_interleave_capture(stm, bufs_in, nframes, false);
cbjack_deinterleave_playback_refill_float(stm, &in_float, nullptr, nframes); cbjack_deinterleave_playback_refill_float(stm, &in_float, nullptr,
nframes);
} }
} else if (stm->devs == OUT_ONLY) { } else if (stm->devs == OUT_ONLY) {
if (stm->out_params.format == CUBEB_SAMPLE_S16NE) { if (stm->out_params.format == CUBEB_SAMPLE_S16NE) {
cbjack_deinterleave_playback_refill_s16ne(stm, nullptr, bufs_out, nframes); cbjack_deinterleave_playback_refill_s16ne(stm, nullptr, bufs_out,
nframes);
} else if (stm->out_params.format == CUBEB_SAMPLE_FLOAT32NE) { } else if (stm->out_params.format == CUBEB_SAMPLE_FLOAT32NE) {
cbjack_deinterleave_playback_refill_float(stm, nullptr, bufs_out, nframes); cbjack_deinterleave_playback_refill_float(stm, nullptr, bufs_out,
nframes);
} }
} }
// unlock stream mutex // unlock stream mutex
@ -439,7 +492,7 @@ cbjack_process(jack_nframes_t nframes, void * arg)
// output silence // output silence
if (stm->devs & OUT_ONLY) { if (stm->devs & OUT_ONLY) {
for (unsigned int c = 0; c < stm->out_params.channels; c++) { for (unsigned int c = 0; c < stm->out_params.channels; c++) {
float* buffer_out = bufs_out[c]; float * buffer_out = bufs_out[c];
for (long f = 0; f < nframes; f++) { for (long f = 0; f < nframes; f++) {
buffer_out[f] = 0.f; buffer_out[f] = 0.f;
} }
@ -448,7 +501,7 @@ cbjack_process(jack_nframes_t nframes, void * arg)
if (stm->devs & IN_ONLY) { if (stm->devs & IN_ONLY) {
// capture silence // capture silence
for (unsigned int c = 0; c < stm->in_params.channels; c++) { for (unsigned int c = 0; c < stm->in_params.channels; c++) {
float* buffer_in = bufs_in[c]; float * buffer_in = bufs_in[c];
for (long f = 0; f < nframes; f++) { for (long f = 0; f < nframes; f++) {
buffer_in[f] = 0.f; buffer_in[f] = 0.f;
} }
@ -461,7 +514,9 @@ cbjack_process(jack_nframes_t nframes, void * arg)
} }
static void static void
cbjack_deinterleave_playback_refill_float(cubeb_stream * stream, float ** in, float ** bufs_out, jack_nframes_t nframes) cbjack_deinterleave_playback_refill_float(cubeb_stream * stream, float ** in,
float ** bufs_out,
jack_nframes_t nframes)
{ {
float * out_interleaved_buffer = nullptr; float * out_interleaved_buffer = nullptr;
@ -472,20 +527,24 @@ cbjack_deinterleave_playback_refill_float(cubeb_stream * stream, float ** in, fl
long done_frames = 0; long done_frames = 0;
long input_frames_count = (in != NULL) ? nframes : 0; long input_frames_count = (in != NULL) ? nframes : 0;
done_frames = cubeb_resampler_fill(stream->resampler, done_frames = cubeb_resampler_fill(
inptr, stream->resampler, inptr, &input_frames_count,
&input_frames_count, (bufs_out != NULL)
(bufs_out != NULL) ? stream->context->out_resampled_interleaved_buffer_float : NULL, ? stream->context->out_resampled_interleaved_buffer_float
needed_frames); : NULL,
needed_frames);
out_interleaved_buffer = stream->context->out_resampled_interleaved_buffer_float; out_interleaved_buffer =
stream->context->out_resampled_interleaved_buffer_float;
if (outptr) { if (outptr) {
// convert interleaved output buffers to contiguous buffers // convert interleaved output buffers to contiguous buffers
for (unsigned int c = 0; c < stream->out_params.channels; c++) { for (unsigned int c = 0; c < stream->out_params.channels; c++) {
float* buffer = bufs_out[c]; float * buffer = bufs_out[c];
for (long f = 0; f < done_frames; f++) { for (long f = 0; f < done_frames; f++) {
buffer[f] = out_interleaved_buffer[(f * stream->out_params.channels) + c] * stream->volume; buffer[f] =
out_interleaved_buffer[(f * stream->out_params.channels) + c] *
stream->volume;
} }
if (done_frames < needed_frames) { if (done_frames < needed_frames) {
// draining // draining
@ -519,7 +578,9 @@ cbjack_deinterleave_playback_refill_float(cubeb_stream * stream, float ** in, fl
} }
static void static void
cbjack_deinterleave_playback_refill_s16ne(cubeb_stream * stream, short ** in, float ** bufs_out, jack_nframes_t nframes) cbjack_deinterleave_playback_refill_s16ne(cubeb_stream * stream, short ** in,
float ** bufs_out,
jack_nframes_t nframes)
{ {
float * out_interleaved_buffer = nullptr; float * out_interleaved_buffer = nullptr;
@ -530,22 +591,28 @@ cbjack_deinterleave_playback_refill_s16ne(cubeb_stream * stream, short ** in, fl
long done_frames = 0; long done_frames = 0;
long input_frames_count = (in != NULL) ? nframes : 0; long input_frames_count = (in != NULL) ? nframes : 0;
done_frames = cubeb_resampler_fill(stream->resampler, done_frames = cubeb_resampler_fill(
inptr, stream->resampler, inptr, &input_frames_count,
&input_frames_count, (bufs_out != NULL)
(bufs_out != NULL) ? stream->context->out_resampled_interleaved_buffer_s16ne : NULL, ? stream->context->out_resampled_interleaved_buffer_s16ne
needed_frames); : NULL,
needed_frames);
s16ne_to_float(stream->context->out_resampled_interleaved_buffer_float, stream->context->out_resampled_interleaved_buffer_s16ne, done_frames * stream->out_params.channels); s16ne_to_float(stream->context->out_resampled_interleaved_buffer_float,
stream->context->out_resampled_interleaved_buffer_s16ne,
done_frames * stream->out_params.channels);
out_interleaved_buffer = stream->context->out_resampled_interleaved_buffer_float; out_interleaved_buffer =
stream->context->out_resampled_interleaved_buffer_float;
if (outptr) { if (outptr) {
// convert interleaved output buffers to contiguous buffers // convert interleaved output buffers to contiguous buffers
for (unsigned int c = 0; c < stream->out_params.channels; c++) { for (unsigned int c = 0; c < stream->out_params.channels; c++) {
float* buffer = bufs_out[c]; float * buffer = bufs_out[c];
for (long f = 0; f < done_frames; f++) { for (long f = 0; f < done_frames; f++) {
buffer[f] = out_interleaved_buffer[(f * stream->out_params.channels) + c] * stream->volume; buffer[f] =
out_interleaved_buffer[(f * stream->out_params.channels) + c] *
stream->volume;
} }
if (done_frames < needed_frames) { if (done_frames < needed_frames) {
// draining // draining
@ -579,20 +646,25 @@ cbjack_deinterleave_playback_refill_s16ne(cubeb_stream * stream, short ** in, fl
} }
static void static void
cbjack_interleave_capture(cubeb_stream * stream, float **in, jack_nframes_t nframes, bool format_mismatch) cbjack_interleave_capture(cubeb_stream * stream, float ** in,
jack_nframes_t nframes, bool format_mismatch)
{ {
float *in_buffer = stream->context->in_float_interleaved_buffer; float * in_buffer = stream->context->in_float_interleaved_buffer;
for (unsigned int c = 0; c < stream->in_params.channels; c++) { for (unsigned int c = 0; c < stream->in_params.channels; c++) {
for (long f = 0; f < nframes; f++) { for (long f = 0; f < nframes; f++) {
in_buffer[(f * stream->in_params.channels) + c] = in[c][f] * stream->volume; in_buffer[(f * stream->in_params.channels) + c] =
in[c][f] * stream->volume;
} }
} }
if (format_mismatch) { if (format_mismatch) {
float_to_s16ne(stream->context->in_resampled_interleaved_buffer_s16ne, in_buffer, nframes * stream->in_params.channels); float_to_s16ne(stream->context->in_resampled_interleaved_buffer_s16ne,
in_buffer, nframes * stream->in_params.channels);
} else { } else {
memset(stream->context->in_resampled_interleaved_buffer_float, 0, (FIFO_SIZE * MAX_CHANNELS * 3) * sizeof(float)); memset(stream->context->in_resampled_interleaved_buffer_float, 0,
memcpy(stream->context->in_resampled_interleaved_buffer_float, in_buffer, (FIFO_SIZE * MAX_CHANNELS * 2) * sizeof(float)); (FIFO_SIZE * MAX_CHANNELS * 3) * sizeof(float));
memcpy(stream->context->in_resampled_interleaved_buffer_float, in_buffer,
(FIFO_SIZE * MAX_CHANNELS * 2) * sizeof(float));
} }
} }
@ -602,7 +674,7 @@ silent_jack_error_callback(char const * /*msg*/)
} }
/*static*/ int /*static*/ int
jack_init (cubeb ** context, char const * context_name) jack_init(cubeb ** context, char const * context_name)
{ {
int r; int r;
@ -619,8 +691,8 @@ jack_init (cubeb ** context, char const * context_name)
return CUBEB_ERROR; return CUBEB_ERROR;
} }
api_jack_set_error_function(silent_jack_error_callback); WRAP(jack_set_error_function)(silent_jack_error_callback);
api_jack_set_info_function(silent_jack_error_callback); WRAP(jack_set_info_function)(silent_jack_error_callback);
ctx->ops = &cbjack_ops; ctx->ops = &cbjack_ops;
@ -633,9 +705,8 @@ jack_init (cubeb ** context, char const * context_name)
if (context_name) if (context_name)
jack_client_name = context_name; jack_client_name = context_name;
ctx->jack_client = api_jack_client_open(jack_client_name, ctx->jack_client =
JackNoStartServer, WRAP(jack_client_open)(jack_client_name, JackNoStartServer, NULL);
NULL);
if (ctx->jack_client == NULL) { if (ctx->jack_client == NULL) {
cbjack_destroy(ctx); cbjack_destroy(ctx);
@ -644,16 +715,17 @@ jack_init (cubeb ** context, char const * context_name)
ctx->jack_xruns = 0; ctx->jack_xruns = 0;
api_jack_set_process_callback (ctx->jack_client, cbjack_process, ctx); WRAP(jack_set_process_callback)(ctx->jack_client, cbjack_process, ctx);
api_jack_set_xrun_callback (ctx->jack_client, cbjack_xrun_callback, ctx); WRAP(jack_set_xrun_callback)(ctx->jack_client, cbjack_xrun_callback, ctx);
api_jack_set_graph_order_callback (ctx->jack_client, cbjack_graph_order_callback, ctx); WRAP(jack_set_graph_order_callback)
(ctx->jack_client, cbjack_graph_order_callback, ctx);
if (api_jack_activate (ctx->jack_client)) { if (WRAP(jack_activate)(ctx->jack_client)) {
cbjack_destroy(ctx); cbjack_destroy(ctx);
return CUBEB_ERROR; return CUBEB_ERROR;
} }
ctx->jack_sample_rate = api_jack_get_sample_rate(ctx->jack_client); ctx->jack_sample_rate = WRAP(jack_get_sample_rate)(ctx->jack_client);
ctx->jack_latency = 128 * 1000 / ctx->jack_sample_rate; ctx->jack_latency = 128 * 1000 / ctx->jack_sample_rate;
ctx->active = true; ctx->active = true;
@ -683,7 +755,8 @@ cbjack_get_latency(cubeb_stream * stm, unsigned int * latency_ms)
} }
static int static int
cbjack_get_min_latency(cubeb * ctx, cubeb_stream_params /*params*/, uint32_t * latency_ms) cbjack_get_min_latency(cubeb * ctx, cubeb_stream_params /*params*/,
uint32_t * latency_ms)
{ {
*latency_ms = ctx->jack_latency; *latency_ms = ctx->jack_latency;
return CUBEB_OK; return CUBEB_OK;
@ -693,18 +766,17 @@ static int
cbjack_get_preferred_sample_rate(cubeb * ctx, uint32_t * rate) cbjack_get_preferred_sample_rate(cubeb * ctx, uint32_t * rate)
{ {
if (!ctx->jack_client) { if (!ctx->jack_client) {
jack_client_t * testclient = api_jack_client_open("test-samplerate", jack_client_t * testclient =
JackNoStartServer, WRAP(jack_client_open)("test-samplerate", JackNoStartServer, NULL);
NULL);
if (!testclient) { if (!testclient) {
return CUBEB_ERROR; return CUBEB_ERROR;
} }
*rate = api_jack_get_sample_rate(testclient); *rate = WRAP(jack_get_sample_rate)(testclient);
api_jack_client_close(testclient); WRAP(jack_client_close)(testclient);
} else { } else {
*rate = api_jack_get_sample_rate(ctx->jack_client); *rate = WRAP(jack_get_sample_rate)(ctx->jack_client);
} }
return CUBEB_OK; return CUBEB_OK;
} }
@ -715,7 +787,7 @@ cbjack_destroy(cubeb * context)
context->active = false; context->active = false;
if (context->jack_client != NULL) if (context->jack_client != NULL)
api_jack_client_close (context->jack_client); WRAP(jack_client_close)(context->jack_client);
if (context->libjack) if (context->libjack)
dlclose(context->libjack); dlclose(context->libjack);
@ -738,30 +810,27 @@ context_alloc_stream(cubeb * context, char const * stream_name)
} }
static int static int
cbjack_stream_init(cubeb * context, cubeb_stream ** stream, char const * stream_name, cbjack_stream_init(cubeb * context, cubeb_stream ** stream,
cubeb_devid input_device, char const * stream_name, cubeb_devid input_device,
cubeb_stream_params * input_stream_params, cubeb_stream_params * input_stream_params,
cubeb_devid output_device, cubeb_devid output_device,
cubeb_stream_params * output_stream_params, cubeb_stream_params * output_stream_params,
unsigned int /*latency_frames*/, unsigned int /*latency_frames*/,
cubeb_data_callback data_callback, cubeb_data_callback data_callback,
cubeb_state_callback state_callback, cubeb_state_callback state_callback, void * user_ptr)
void * user_ptr)
{ {
int stream_actual_rate = 0; int stream_actual_rate = 0;
int jack_rate = api_jack_get_sample_rate(context->jack_client); int jack_rate = WRAP(jack_get_sample_rate)(context->jack_client);
if (output_stream_params if (output_stream_params &&
&& (output_stream_params->format != CUBEB_SAMPLE_FLOAT32NE && (output_stream_params->format != CUBEB_SAMPLE_FLOAT32NE &&
output_stream_params->format != CUBEB_SAMPLE_S16NE) output_stream_params->format != CUBEB_SAMPLE_S16NE)) {
) {
return CUBEB_ERROR_INVALID_FORMAT; return CUBEB_ERROR_INVALID_FORMAT;
} }
if (input_stream_params if (input_stream_params &&
&& (input_stream_params->format != CUBEB_SAMPLE_FLOAT32NE && (input_stream_params->format != CUBEB_SAMPLE_FLOAT32NE &&
input_stream_params->format != CUBEB_SAMPLE_S16NE) input_stream_params->format != CUBEB_SAMPLE_S16NE)) {
) {
return CUBEB_ERROR_INVALID_FORMAT; return CUBEB_ERROR_INVALID_FORMAT;
} }
@ -771,8 +840,10 @@ cbjack_stream_init(cubeb * context, cubeb_stream ** stream, char const * stream_
} }
// Loopback is unsupported // Loopback is unsupported
if ((input_stream_params && (input_stream_params->prefs & CUBEB_STREAM_PREF_LOOPBACK)) || if ((input_stream_params &&
(output_stream_params && (output_stream_params->prefs & CUBEB_STREAM_PREF_LOOPBACK))) { (input_stream_params->prefs & CUBEB_STREAM_PREF_LOOPBACK)) ||
(output_stream_params &&
(output_stream_params->prefs & CUBEB_STREAM_PREF_LOOPBACK))) {
return CUBEB_ERROR_NOT_SUPPORTED; return CUBEB_ERROR_NOT_SUPPORTED;
} }
@ -841,7 +912,7 @@ cbjack_stream_init(cubeb * context, cubeb_stream ** stream, char const * stream_
stm->state_callback = state_callback; stm->state_callback = state_callback;
stm->position = 0; stm->position = 0;
stm->volume = 1.0f; stm->volume = 1.0f;
context->jack_buffer_size = api_jack_get_buffer_size(context->jack_client); context->jack_buffer_size = WRAP(jack_get_buffer_size)(context->jack_client);
context->fragment_size = context->jack_buffer_size; context->fragment_size = context->jack_buffer_size;
if (stm->devs == NONE) { if (stm->devs == NONE) {
@ -852,29 +923,17 @@ cbjack_stream_init(cubeb * context, cubeb_stream ** stream, char const * stream_
stm->resampler = NULL; stm->resampler = NULL;
if (stm->devs == DUPLEX) { if (stm->devs == DUPLEX) {
stm->resampler = cubeb_resampler_create(stm, stm->resampler = cubeb_resampler_create(
&stm->in_params, stm, &stm->in_params, &stm->out_params, stream_actual_rate,
&stm->out_params, stm->data_callback, stm->user_ptr, CUBEB_RESAMPLER_QUALITY_DESKTOP);
stream_actual_rate,
stm->data_callback,
stm->user_ptr,
CUBEB_RESAMPLER_QUALITY_DESKTOP);
} else if (stm->devs == IN_ONLY) { } else if (stm->devs == IN_ONLY) {
stm->resampler = cubeb_resampler_create(stm, stm->resampler = cubeb_resampler_create(
&stm->in_params, stm, &stm->in_params, nullptr, stream_actual_rate, stm->data_callback,
nullptr, stm->user_ptr, CUBEB_RESAMPLER_QUALITY_DESKTOP);
stream_actual_rate,
stm->data_callback,
stm->user_ptr,
CUBEB_RESAMPLER_QUALITY_DESKTOP);
} else if (stm->devs == OUT_ONLY) { } else if (stm->devs == OUT_ONLY) {
stm->resampler = cubeb_resampler_create(stm, stm->resampler = cubeb_resampler_create(
nullptr, stm, nullptr, &stm->out_params, stream_actual_rate, stm->data_callback,
&stm->out_params, stm->user_ptr, CUBEB_RESAMPLER_QUALITY_DESKTOP);
stream_actual_rate,
stm->data_callback,
stm->user_ptr,
CUBEB_RESAMPLER_QUALITY_DESKTOP);
} }
if (!stm->resampler) { if (!stm->resampler) {
@ -887,11 +946,18 @@ cbjack_stream_init(cubeb * context, cubeb_stream ** stream, char const * stream_
for (unsigned int c = 0; c < stm->out_params.channels; c++) { for (unsigned int c = 0; c < stm->out_params.channels; c++) {
char portname[256]; char portname[256];
snprintf(portname, 255, "%s_out_%d", stm->stream_name, c); snprintf(portname, 255, "%s_out_%d", stm->stream_name, c);
stm->output_ports[c] = api_jack_port_register(stm->context->jack_client, stm->output_ports[c] = WRAP(jack_port_register)(
portname, stm->context->jack_client, portname, JACK_DEFAULT_AUDIO_TYPE,
JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0);
JackPortIsOutput, if (!(output_stream_params->prefs &
0); CUBEB_STREAM_PREF_JACK_NO_AUTO_CONNECT)) {
if (cbjack_connect_ports(stm, CBJACK_CP_OPTIONS_SKIP_INPUT) !=
CUBEB_OK) {
pthread_mutex_unlock(&stm->mutex);
cbjack_stream_destroy(stm);
return CUBEB_ERROR;
}
}
} }
} }
@ -899,19 +965,18 @@ cbjack_stream_init(cubeb * context, cubeb_stream ** stream, char const * stream_
for (unsigned int c = 0; c < stm->in_params.channels; c++) { for (unsigned int c = 0; c < stm->in_params.channels; c++) {
char portname[256]; char portname[256];
snprintf(portname, 255, "%s_in_%d", stm->stream_name, c); snprintf(portname, 255, "%s_in_%d", stm->stream_name, c);
stm->input_ports[c] = api_jack_port_register(stm->context->jack_client, stm->input_ports[c] =
portname, WRAP(jack_port_register)(stm->context->jack_client, portname,
JACK_DEFAULT_AUDIO_TYPE, JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput, 0);
JackPortIsInput, if (!(input_stream_params->prefs &
0); CUBEB_STREAM_PREF_JACK_NO_AUTO_CONNECT)) {
} if (cbjack_connect_ports(stm, CBJACK_CP_OPTIONS_SKIP_OUTPUT) !=
} CUBEB_OK) {
pthread_mutex_unlock(&stm->mutex);
if (!input_stream_params->prefs & CUBEB_STREAM_PREF_JACK_NO_AUTO_CONNECT) { cbjack_stream_destroy(stm);
if (cbjack_connect_ports(stm) != CUBEB_OK) { return CUBEB_ERROR;
pthread_mutex_unlock(&stm->mutex); }
cbjack_stream_destroy(stm); }
return CUBEB_ERROR;
} }
} }
@ -933,7 +998,8 @@ cbjack_stream_destroy(cubeb_stream * stream)
if (stream->devs == DUPLEX || stream->devs == OUT_ONLY) { if (stream->devs == DUPLEX || stream->devs == OUT_ONLY) {
for (unsigned int c = 0; c < stream->out_params.channels; c++) { for (unsigned int c = 0; c < stream->out_params.channels; c++) {
if (stream->output_ports[c]) { if (stream->output_ports[c]) {
api_jack_port_unregister (stream->context->jack_client, stream->output_ports[c]); WRAP(jack_port_unregister)
(stream->context->jack_client, stream->output_ports[c]);
stream->output_ports[c] = NULL; stream->output_ports[c] = NULL;
} }
} }
@ -942,7 +1008,8 @@ cbjack_stream_destroy(cubeb_stream * stream)
if (stream->devs == DUPLEX || stream->devs == IN_ONLY) { if (stream->devs == DUPLEX || stream->devs == IN_ONLY) {
for (unsigned int c = 0; c < stream->in_params.channels; c++) { for (unsigned int c = 0; c < stream->in_params.channels; c++) {
if (stream->input_ports[c]) { if (stream->input_ports[c]) {
api_jack_port_unregister (stream->context->jack_client, stream->input_ports[c]); WRAP(jack_port_unregister)
(stream->context->jack_client, stream->input_ports[c]);
stream->input_ports[c] = NULL; stream->input_ports[c] = NULL;
} }
} }
@ -987,7 +1054,8 @@ cbjack_stream_set_volume(cubeb_stream * stm, float volume)
} }
static int static int
cbjack_stream_get_current_device(cubeb_stream * stm, cubeb_device ** const device) cbjack_stream_get_current_device(cubeb_stream * stm,
cubeb_device ** const device)
{ {
*device = (cubeb_device *)calloc(1, sizeof(cubeb_device)); *device = (cubeb_device *)calloc(1, sizeof(cubeb_device));
if (*device == NULL) if (*device == NULL)
@ -1012,8 +1080,7 @@ cbjack_stream_get_current_device(cubeb_stream * stm, cubeb_device ** const devic
} }
static int static int
cbjack_stream_device_destroy(cubeb_stream * /*stream*/, cbjack_stream_device_destroy(cubeb_stream * /*stream*/, cubeb_device * device)
cubeb_device * device)
{ {
if (device->input_name) if (device->input_name)
free(device->input_name); free(device->input_name);
@ -1042,7 +1109,7 @@ cbjack_enumerate_devices(cubeb * context, cubeb_device_type type,
if (type & CUBEB_DEVICE_TYPE_OUTPUT) { if (type & CUBEB_DEVICE_TYPE_OUTPUT) {
cubeb_device_info * cur = &devices[collection->count]; cubeb_device_info * cur = &devices[collection->count];
cur->device_id = JACK_DEFAULT_OUT; cur->device_id = JACK_DEFAULT_OUT;
cur->devid = (cubeb_devid) cur->device_id; cur->devid = (cubeb_devid)cur->device_id;
cur->friendly_name = JACK_DEFAULT_OUT; cur->friendly_name = JACK_DEFAULT_OUT;
cur->group_id = JACK_DEFAULT_OUT; cur->group_id = JACK_DEFAULT_OUT;
cur->vendor_name = JACK_DEFAULT_OUT; cur->vendor_name = JACK_DEFAULT_OUT;
@ -1057,13 +1124,13 @@ cbjack_enumerate_devices(cubeb * context, cubeb_device_type type,
cur->default_rate = rate; cur->default_rate = rate;
cur->latency_lo = 0; cur->latency_lo = 0;
cur->latency_hi = 0; cur->latency_hi = 0;
collection->count +=1 ; collection->count += 1;
} }
if (type & CUBEB_DEVICE_TYPE_INPUT) { if (type & CUBEB_DEVICE_TYPE_INPUT) {
cubeb_device_info * cur = &devices[collection->count]; cubeb_device_info * cur = &devices[collection->count];
cur->device_id = JACK_DEFAULT_IN; cur->device_id = JACK_DEFAULT_IN;
cur->devid = (cubeb_devid) cur->device_id; cur->devid = (cubeb_devid)cur->device_id;
cur->friendly_name = JACK_DEFAULT_IN; cur->friendly_name = JACK_DEFAULT_IN;
cur->group_id = JACK_DEFAULT_IN; cur->group_id = JACK_DEFAULT_IN;
cur->vendor_name = JACK_DEFAULT_IN; cur->vendor_name = JACK_DEFAULT_IN;
@ -1091,6 +1158,6 @@ cbjack_device_collection_destroy(cubeb * /*ctx*/,
cubeb_device_collection * collection) cubeb_device_collection * collection)
{ {
XASSERT(collection); XASSERT(collection);
delete [] collection->device; delete[] collection->device;
return CUBEB_OK; return CUBEB_OK;
} }

View file

@ -4,15 +4,15 @@
* This program is made available under an ISC-style license. See the * This program is made available under an ISC-style license. See the
* accompanying file LICENSE for details. * accompanying file LICENSE for details.
*/ */
#include <math.h>
#include <stdio.h> #include <stdio.h>
#include <string.h> #include <string.h>
#include <math.h>
#include <sys/fmutex.h> #include <sys/fmutex.h>
#include <kai.h> #include <kai.h>
#include "cubeb/cubeb.h"
#include "cubeb-internal.h" #include "cubeb-internal.h"
#include "cubeb/cubeb.h"
/* We don't support more than 2 channels in KAI */ /* We don't support more than 2 channels in KAI */
#define MAX_CHANNELS 2 #define MAX_CHANNELS 2
@ -59,7 +59,8 @@ bytes_to_frames(long bytes, cubeb_stream_params params)
return bytes / 2 / params.channels; /* 2 bytes per frame */ return bytes / 2 / params.channels; /* 2 bytes per frame */
} }
static void kai_destroy(cubeb * ctx); static void
kai_destroy(cubeb * ctx);
/*static*/ int /*static*/ int
kai_init(cubeb ** context, char const * context_name) kai_init(cubeb ** context, char const * context_name)
@ -97,7 +98,7 @@ kai_destroy(cubeb * ctx)
} }
static void static void
float_to_s16ne(int16_t *dst, float *src, size_t n) float_to_s16ne(int16_t * dst, float * src, size_t n)
{ {
long l; long l;
@ -115,14 +116,13 @@ static ULONG APIENTRY
kai_callback(PVOID cbdata, PVOID buffer, ULONG len) kai_callback(PVOID cbdata, PVOID buffer, ULONG len)
{ {
cubeb_stream * stm = cbdata; cubeb_stream * stm = cbdata;
void *p; void * p;
long wanted_frames; long wanted_frames;
long frames; long frames;
float soft_volume; float soft_volume;
int elements = len / sizeof(int16_t); int elements = len / sizeof(int16_t);
p = stm->params.format == CUBEB_SAMPLE_FLOAT32NE p = stm->params.format == CUBEB_SAMPLE_FLOAT32NE ? stm->float_buffer : buffer;
? stm->float_buffer : buffer;
wanted_frames = bytes_to_frames(len, stm->params); wanted_frames = bytes_to_frames(len, stm->params);
frames = stm->data_callback(stm, stm->user_ptr, NULL, p, wanted_frames); frames = stm->data_callback(stm, stm->user_ptr, NULL, p, wanted_frames);
@ -139,7 +139,7 @@ kai_callback(PVOID cbdata, PVOID buffer, ULONG len)
float_to_s16ne(buffer, p, elements); float_to_s16ne(buffer, p, elements);
if (soft_volume != -1.0f) { if (soft_volume != -1.0f) {
int16_t *b = buffer; int16_t * b = buffer;
int i; int i;
for (i = 0; i < elements; i++) for (i = 0; i < elements; i++)
@ -149,12 +149,12 @@ kai_callback(PVOID cbdata, PVOID buffer, ULONG len)
return frames_to_bytes(frames, stm->params); return frames_to_bytes(frames, stm->params);
} }
static void kai_stream_destroy(cubeb_stream * stm); static void
kai_stream_destroy(cubeb_stream * stm);
static int static int
kai_stream_init(cubeb * context, cubeb_stream ** stream, kai_stream_init(cubeb * context, cubeb_stream ** stream,
char const * stream_name, char const * stream_name, cubeb_devid input_device,
cubeb_devid input_device,
cubeb_stream_params * input_stream_params, cubeb_stream_params * input_stream_params,
cubeb_devid output_device, cubeb_devid output_device,
cubeb_stream_params * output_stream_params, cubeb_stream_params * output_stream_params,
@ -202,17 +202,17 @@ kai_stream_init(cubeb * context, cubeb_stream ** stream,
return CUBEB_ERROR; return CUBEB_ERROR;
} }
wanted_spec.usDeviceIndex = 0; wanted_spec.usDeviceIndex = 0;
wanted_spec.ulType = KAIT_PLAY; wanted_spec.ulType = KAIT_PLAY;
wanted_spec.ulBitsPerSample = BPS_16; wanted_spec.ulBitsPerSample = BPS_16;
wanted_spec.ulSamplingRate = stm->params.rate; wanted_spec.ulSamplingRate = stm->params.rate;
wanted_spec.ulDataFormat = MCI_WAVE_FORMAT_PCM; wanted_spec.ulDataFormat = MCI_WAVE_FORMAT_PCM;
wanted_spec.ulChannels = stm->params.channels; wanted_spec.ulChannels = stm->params.channels;
wanted_spec.ulNumBuffers = NBUFS; wanted_spec.ulNumBuffers = NBUFS;
wanted_spec.ulBufferSize = frames_to_bytes(FRAME_SIZE, stm->params); wanted_spec.ulBufferSize = frames_to_bytes(FRAME_SIZE, stm->params);
wanted_spec.fShareable = TRUE; wanted_spec.fShareable = TRUE;
wanted_spec.pfnCallBack = kai_callback; wanted_spec.pfnCallBack = kai_callback;
wanted_spec.pCallBackData = stm; wanted_spec.pCallBackData = stm;
if (kaiOpen(&wanted_spec, &stm->spec, &stm->hkai)) { if (kaiOpen(&wanted_spec, &stm->spec, &stm->hkai)) {
_fmutex_close(&stm->mutex); _fmutex_close(&stm->mutex);
@ -265,17 +265,17 @@ kai_get_preferred_sample_rate(cubeb * ctx, uint32_t * rate)
params.rate = 48000; params.rate = 48000;
params.channels = 2; params.channels = 2;
wanted_spec.usDeviceIndex = 0; wanted_spec.usDeviceIndex = 0;
wanted_spec.ulType = KAIT_PLAY; wanted_spec.ulType = KAIT_PLAY;
wanted_spec.ulBitsPerSample = BPS_16; wanted_spec.ulBitsPerSample = BPS_16;
wanted_spec.ulSamplingRate = params.rate; wanted_spec.ulSamplingRate = params.rate;
wanted_spec.ulDataFormat = MCI_WAVE_FORMAT_PCM; wanted_spec.ulDataFormat = MCI_WAVE_FORMAT_PCM;
wanted_spec.ulChannels = params.channels; wanted_spec.ulChannels = params.channels;
wanted_spec.ulNumBuffers = NBUFS; wanted_spec.ulNumBuffers = NBUFS;
wanted_spec.ulBufferSize = frames_to_bytes(FRAME_SIZE, params); wanted_spec.ulBufferSize = frames_to_bytes(FRAME_SIZE, params);
wanted_spec.fShareable = TRUE; wanted_spec.fShareable = TRUE;
wanted_spec.pfnCallBack = kai_callback; wanted_spec.pfnCallBack = kai_callback;
wanted_spec.pCallBackData = NULL; wanted_spec.pCallBackData = NULL;
/* Test 48KHz */ /* Test 48KHz */
if (kaiOpen(&wanted_spec, &spec, &hkai)) { if (kaiOpen(&wanted_spec, &spec, &hkai)) {
@ -328,8 +328,8 @@ kai_stream_get_latency(cubeb_stream * stm, uint32_t * latency)
{ {
/* Out of buffers, one is being played, the others are being filled. /* Out of buffers, one is being played, the others are being filled.
So there is as much latency as total buffers - 1. */ So there is as much latency as total buffers - 1. */
*latency = bytes_to_frames(stm->spec.ulBufferSize, stm->params) *latency = bytes_to_frames(stm->spec.ulBufferSize, stm->params) *
* (stm->spec.ulNumBuffers - 1); (stm->spec.ulNumBuffers - 1);
return CUBEB_OK; return CUBEB_OK;
} }
@ -345,27 +345,25 @@ kai_stream_set_volume(cubeb_stream * stm, float volume)
} }
static struct cubeb_ops const kai_ops = { static struct cubeb_ops const kai_ops = {
/*.init =*/ kai_init, /*.init =*/kai_init,
/*.get_backend_id =*/ kai_get_backend_id, /*.get_backend_id =*/kai_get_backend_id,
/*.get_max_channel_count=*/ kai_get_max_channel_count, /*.get_max_channel_count=*/kai_get_max_channel_count,
/*.get_min_latency=*/ kai_get_min_latency, /*.get_min_latency=*/kai_get_min_latency,
/*.get_preferred_sample_rate =*/ kai_get_preferred_sample_rate, /*.get_preferred_sample_rate =*/kai_get_preferred_sample_rate,
/*.get_preferred_channel_layout =*/ NULL, /*.get_preferred_channel_layout =*/NULL,
/*.enumerate_devices =*/ NULL, /*.enumerate_devices =*/NULL,
/*.device_collection_destroy =*/ NULL, /*.device_collection_destroy =*/NULL,
/*.destroy =*/ kai_destroy, /*.destroy =*/kai_destroy,
/*.stream_init =*/ kai_stream_init, /*.stream_init =*/kai_stream_init,
/*.stream_destroy =*/ kai_stream_destroy, /*.stream_destroy =*/kai_stream_destroy,
/*.stream_start =*/ kai_stream_start, /*.stream_start =*/kai_stream_start,
/*.stream_stop =*/ kai_stream_stop, /*.stream_stop =*/kai_stream_stop,
/*.stream_reset_default_device =*/ NULL, /*.stream_get_position =*/kai_stream_get_position,
/*.stream_get_position =*/ kai_stream_get_position, /*.stream_get_latency = */ kai_stream_get_latency,
/*.stream_get_latency = */ kai_stream_get_latency, /*.stream_get_input_latency = */ NULL,
/*.stream_get_input_latency = */ NULL, /*.stream_set_volume =*/kai_stream_set_volume,
/*.stream_set_volume =*/ kai_stream_set_volume, /*.stream_set_name =*/NULL,
/*.stream_set_name =*/ NULL, /*.stream_get_current_device =*/NULL,
/*.stream_get_current_device =*/ NULL, /*.stream_device_destroy =*/NULL,
/*.stream_device_destroy =*/ NULL, /*.stream_register_device_changed_callback=*/NULL,
/*.stream_register_device_changed_callback=*/ NULL, /*.register_device_collection_changed=*/NULL};
/*.register_device_collection_changed=*/ NULL
};

View file

@ -27,17 +27,13 @@ const size_t CUBEB_LOG_MESSAGE_QUEUE_DEPTH = 40;
#define CUBEB_LOG_BATCH_PRINT_INTERVAL_MS 10 #define CUBEB_LOG_BATCH_PRINT_INTERVAL_MS 10
/** /**
* This wraps an inline buffer, that represents a log message, that must be * This wraps an inline buffer, that represents a log message, that must be
* null-terminated. * null-terminated.
* This class should not use system calls or other potentially blocking code. * This class should not use system calls or other potentially blocking code.
*/ */
class cubeb_log_message class cubeb_log_message {
{
public: public:
cubeb_log_message() cubeb_log_message() { *storage = '\0'; }
{
*storage = '\0';
}
cubeb_log_message(char const str[CUBEB_LOG_MESSAGE_MAX_SIZE]) cubeb_log_message(char const str[CUBEB_LOG_MESSAGE_MAX_SIZE])
{ {
size_t length = strlen(str); size_t length = strlen(str);
@ -49,20 +45,19 @@ public:
PodCopy(storage, str, length); PodCopy(storage, str, length);
storage[length] = '\0'; storage[length] = '\0';
} }
char const * get() { char const * get() { return storage; }
return storage;
}
private: private:
char storage[CUBEB_LOG_MESSAGE_MAX_SIZE]; char storage[CUBEB_LOG_MESSAGE_MAX_SIZE];
}; };
/** Lock-free asynchronous logger, made so that logging from a /** Lock-free asynchronous logger, made so that logging from a
* real-time audio callback does not block the audio thread. */ * real-time audio callback does not block the audio thread. */
class cubeb_async_logger class cubeb_async_logger {
{
public: public:
/* This is thread-safe since C++11 */ /* This is thread-safe since C++11 */
static cubeb_async_logger & get() { static cubeb_async_logger & get()
{
static cubeb_async_logger instance; static cubeb_async_logger instance;
return instance; return instance;
} }
@ -85,8 +80,7 @@ public:
timespec sleep_duration = sleep_for; timespec sleep_duration = sleep_for;
timespec remainder; timespec remainder;
do { do {
if (nanosleep(&sleep_duration, &remainder) == 0 || if (nanosleep(&sleep_duration, &remainder) == 0 || errno != EINTR) {
errno != EINTR) {
break; break;
} }
sleep_duration = remainder; sleep_duration = remainder;
@ -97,29 +91,22 @@ public:
} }
// Tell the underlying queue the producer thread has changed, so it does not // Tell the underlying queue the producer thread has changed, so it does not
// assert in debug. This should be called with the thread stopped. // assert in debug. This should be called with the thread stopped.
void reset_producer_thread() void reset_producer_thread() { msg_queue.reset_thread_ids(); }
{
msg_queue.reset_thread_ids();
}
private: private:
#ifndef _WIN32 #ifndef _WIN32
const struct timespec sleep_for = { const struct timespec sleep_for = {
CUBEB_LOG_BATCH_PRINT_INTERVAL_MS/1000, CUBEB_LOG_BATCH_PRINT_INTERVAL_MS / 1000,
(CUBEB_LOG_BATCH_PRINT_INTERVAL_MS%1000)*1000*1000 (CUBEB_LOG_BATCH_PRINT_INTERVAL_MS % 1000) * 1000 * 1000};
};
#endif #endif
cubeb_async_logger() cubeb_async_logger() : msg_queue(CUBEB_LOG_MESSAGE_QUEUE_DEPTH) { run(); }
: msg_queue(CUBEB_LOG_MESSAGE_QUEUE_DEPTH)
{
run();
}
/** This is quite a big data structure, but is only instantiated if the /** This is quite a big data structure, but is only instantiated if the
* asynchronous logger is used.*/ * asynchronous logger is used.*/
lock_free_queue<cubeb_log_message> msg_queue; lock_free_queue<cubeb_log_message> msg_queue;
}; };
void
void cubeb_async_log(char const * fmt, ...) cubeb_async_log(char const * fmt, ...)
{ {
if (!g_cubeb_log_callback) { if (!g_cubeb_log_callback) {
return; return;
@ -135,7 +122,8 @@ void cubeb_async_log(char const * fmt, ...)
va_end(args); va_end(args);
} }
void cubeb_async_log_reset_threads() void
cubeb_async_log_reset_threads()
{ {
if (!g_cubeb_log_callback) { if (!g_cubeb_log_callback) {
return; return;

View file

@ -19,18 +19,23 @@ extern "C" {
#if defined(__FILE_NAME__) #if defined(__FILE_NAME__)
#define __FILENAME__ __FILE_NAME__ #define __FILENAME__ __FILE_NAME__
#else #else
#define __FILENAME__ (__builtin_strrchr(__FILE__, '/') ? __builtin_strrchr(__FILE__, '/') + 1 : __FILE__) #define __FILENAME__ \
(__builtin_strrchr(__FILE__, '/') ? __builtin_strrchr(__FILE__, '/') + 1 \
: __FILE__)
#endif #endif
#else #else
#define PRINTF_FORMAT(fmt, args) #define PRINTF_FORMAT(fmt, args)
#include <string.h> #include <string.h>
#define __FILENAME__ (strrchr(__FILE__, '/') ? strrchr(__FILE__, '/') + 1 : __FILE__) #define __FILENAME__ \
(strrchr(__FILE__, '/') ? strrchr(__FILE__, '/') + 1 : __FILE__)
#endif #endif
extern cubeb_log_level g_cubeb_log_level; extern cubeb_log_level g_cubeb_log_level;
extern cubeb_log_callback g_cubeb_log_callback PRINTF_FORMAT(1, 2); extern cubeb_log_callback g_cubeb_log_callback PRINTF_FORMAT(1, 2);
void cubeb_async_log(const char * fmt, ...); void
void cubeb_async_log_reset_threads(); cubeb_async_log(const char * fmt, ...);
void
cubeb_async_log_reset_threads();
#ifdef __cplusplus #ifdef __cplusplus
} }
@ -39,16 +44,19 @@ void cubeb_async_log_reset_threads();
#define LOGV(msg, ...) LOG_INTERNAL(CUBEB_LOG_VERBOSE, msg, ##__VA_ARGS__) #define LOGV(msg, ...) LOG_INTERNAL(CUBEB_LOG_VERBOSE, msg, ##__VA_ARGS__)
#define LOG(msg, ...) LOG_INTERNAL(CUBEB_LOG_NORMAL, msg, ##__VA_ARGS__) #define LOG(msg, ...) LOG_INTERNAL(CUBEB_LOG_NORMAL, msg, ##__VA_ARGS__)
#define LOG_INTERNAL(level, fmt, ...) do { \ #define LOG_INTERNAL(level, fmt, ...) \
if (g_cubeb_log_callback && level <= g_cubeb_log_level) { \ do { \
g_cubeb_log_callback("%s:%d: " fmt "\n", __FILENAME__, __LINE__, ##__VA_ARGS__); \ if (g_cubeb_log_callback && level <= g_cubeb_log_level) { \
} \ g_cubeb_log_callback("%s:%d: " fmt "\n", __FILENAME__, __LINE__, \
} while(0) ##__VA_ARGS__); \
} \
} while (0)
/* Asynchronous verbose logging, to log in real-time callbacks. */ /* Asynchronous verbose logging, to log in real-time callbacks. */
#define ALOGV(fmt, ...) \ /* Should not be used on android due to the use of global/static variables. */
do { \ #define ALOGV(fmt, ...) \
cubeb_async_log(fmt, ##__VA_ARGS__); \ do { \
} while(0) cubeb_async_log(fmt, ##__VA_ARGS__); \
} while (0)
#endif // CUBEB_LOG #endif // CUBEB_LOG

View file

@ -9,6 +9,9 @@
#define NOMINMAX #define NOMINMAX
#include "cubeb_mixer.h"
#include "cubeb-internal.h"
#include "cubeb_utils.h"
#include <algorithm> #include <algorithm>
#include <cassert> #include <cassert>
#include <climits> #include <climits>
@ -16,67 +19,67 @@
#include <cstdlib> #include <cstdlib>
#include <memory> #include <memory>
#include <type_traits> #include <type_traits>
#include "cubeb-internal.h"
#include "cubeb_mixer.h"
#include "cubeb_utils.h"
#ifndef FF_ARRAY_ELEMS #ifndef FF_ARRAY_ELEMS
#define FF_ARRAY_ELEMS(a) (sizeof(a) / sizeof((a)[0])) #define FF_ARRAY_ELEMS(a) (sizeof(a) / sizeof((a)[0]))
#endif #endif
#define CHANNELS_MAX 32 #define CHANNELS_MAX 32
#define FRONT_LEFT 0 #define FRONT_LEFT 0
#define FRONT_RIGHT 1 #define FRONT_RIGHT 1
#define FRONT_CENTER 2 #define FRONT_CENTER 2
#define LOW_FREQUENCY 3 #define LOW_FREQUENCY 3
#define BACK_LEFT 4 #define BACK_LEFT 4
#define BACK_RIGHT 5 #define BACK_RIGHT 5
#define FRONT_LEFT_OF_CENTER 6 #define FRONT_LEFT_OF_CENTER 6
#define FRONT_RIGHT_OF_CENTER 7 #define FRONT_RIGHT_OF_CENTER 7
#define BACK_CENTER 8 #define BACK_CENTER 8
#define SIDE_LEFT 9 #define SIDE_LEFT 9
#define SIDE_RIGHT 10 #define SIDE_RIGHT 10
#define TOP_CENTER 11 #define TOP_CENTER 11
#define TOP_FRONT_LEFT 12 #define TOP_FRONT_LEFT 12
#define TOP_FRONT_CENTER 13 #define TOP_FRONT_CENTER 13
#define TOP_FRONT_RIGHT 14 #define TOP_FRONT_RIGHT 14
#define TOP_BACK_LEFT 15 #define TOP_BACK_LEFT 15
#define TOP_BACK_CENTER 16 #define TOP_BACK_CENTER 16
#define TOP_BACK_RIGHT 17 #define TOP_BACK_RIGHT 17
#define NUM_NAMED_CHANNELS 18 #define NUM_NAMED_CHANNELS 18
#ifndef M_SQRT1_2 #ifndef M_SQRT1_2
#define M_SQRT1_2 0.70710678118654752440 /* 1/sqrt(2) */ #define M_SQRT1_2 0.70710678118654752440 /* 1/sqrt(2) */
#endif #endif
#ifndef M_SQRT2 #ifndef M_SQRT2
#define M_SQRT2 1.41421356237309504880 /* sqrt(2) */ #define M_SQRT2 1.41421356237309504880 /* sqrt(2) */
#endif #endif
#define SQRT3_2 1.22474487139158904909 /* sqrt(3/2) */ #define SQRT3_2 1.22474487139158904909 /* sqrt(3/2) */
#define C30DB M_SQRT2 #define C30DB M_SQRT2
#define C15DB 1.189207115 #define C15DB 1.189207115
#define C__0DB 1.0 #define C__0DB 1.0
#define C_15DB 0.840896415 #define C_15DB 0.840896415
#define C_30DB M_SQRT1_2 #define C_30DB M_SQRT1_2
#define C_45DB 0.594603558 #define C_45DB 0.594603558
#define C_60DB 0.5 #define C_60DB 0.5
static cubeb_channel_layout static cubeb_channel_layout
cubeb_channel_layout_check(cubeb_channel_layout l, uint32_t c) cubeb_channel_layout_check(cubeb_channel_layout l, uint32_t c)
{ {
if (l == CUBEB_LAYOUT_UNDEFINED) { if (l == CUBEB_LAYOUT_UNDEFINED) {
switch (c) { switch (c) {
case 1: return CUBEB_LAYOUT_MONO; case 1:
case 2: return CUBEB_LAYOUT_STEREO; return CUBEB_LAYOUT_MONO;
} case 2:
return CUBEB_LAYOUT_STEREO;
} }
return l; }
return l;
} }
unsigned int cubeb_channel_layout_nb_channels(cubeb_channel_layout x) unsigned int
cubeb_channel_layout_nb_channels(cubeb_channel_layout x)
{ {
#if __GNUC__ || __clang__ #if __GNUC__ || __clang__
return __builtin_popcount (x); return __builtin_popcount(x);
#else #else
x -= (x >> 1) & 0x55555555; x -= (x >> 1) & 0x55555555;
x = (x & 0x33333333) + ((x >> 2) & 0x33333333); x = (x & 0x33333333) + ((x >> 2) & 0x33333333);
@ -87,16 +90,12 @@ unsigned int cubeb_channel_layout_nb_channels(cubeb_channel_layout x)
} }
struct MixerContext { struct MixerContext {
MixerContext(cubeb_sample_format f, MixerContext(cubeb_sample_format f, uint32_t in_channels,
uint32_t in_channels, cubeb_channel_layout in, uint32_t out_channels,
cubeb_channel_layout in,
uint32_t out_channels,
cubeb_channel_layout out) cubeb_channel_layout out)
: _format(f) : _format(f), _in_ch_layout(cubeb_channel_layout_check(in, in_channels)),
, _in_ch_layout(cubeb_channel_layout_check(in, in_channels)) _out_ch_layout(cubeb_channel_layout_check(out, out_channels)),
, _out_ch_layout(cubeb_channel_layout_check(out, out_channels)) _in_ch_count(in_channels), _out_ch_count(out_channels)
, _in_ch_count(in_channels)
, _out_ch_count(out_channels)
{ {
if (in_channels != cubeb_channel_layout_nb_channels(in) || if (in_channels != cubeb_channel_layout_nb_channels(in) ||
out_channels != cubeb_channel_layout_nb_channels(out)) { out_channels != cubeb_channel_layout_nb_channels(out)) {
@ -159,24 +158,30 @@ struct MixerContext {
int init(); int init();
const cubeb_sample_format _format; const cubeb_sample_format _format;
const cubeb_channel_layout _in_ch_layout; ///< input channel layout const cubeb_channel_layout _in_ch_layout; ///< input channel layout
const cubeb_channel_layout _out_ch_layout; ///< output channel layout const cubeb_channel_layout _out_ch_layout; ///< output channel layout
const uint32_t _in_ch_count; ///< input channel count const uint32_t _in_ch_count; ///< input channel count
const uint32_t _out_ch_count; ///< output channel count const uint32_t _out_ch_count; ///< output channel count
const float _surround_mix_level = C_30DB; ///< surround mixing level const float _surround_mix_level = C_30DB; ///< surround mixing level
const float _center_mix_level = C_30DB; ///< center mixing level const float _center_mix_level = C_30DB; ///< center mixing level
const float _lfe_mix_level = 1; ///< LFE mixing level const float _lfe_mix_level = 1; ///< LFE mixing level
double _matrix[CHANNELS_MAX][CHANNELS_MAX] = {{ 0 }}; ///< floating point rematrixing coefficients double _matrix[CHANNELS_MAX][CHANNELS_MAX] = {
float _matrix_flt[CHANNELS_MAX][CHANNELS_MAX] = {{ 0 }}; ///< single precision floating point rematrixing coefficients {0}}; ///< floating point rematrixing coefficients
int32_t _matrix32[CHANNELS_MAX][CHANNELS_MAX] = {{ 0 }}; ///< 17.15 fixed point rematrixing coefficients float _matrix_flt[CHANNELS_MAX][CHANNELS_MAX] = {
uint8_t _matrix_ch[CHANNELS_MAX][CHANNELS_MAX+1] = {{ 0 }}; ///< Lists of input channels per output channel that have non zero rematrixing coefficients {0}}; ///< single precision floating point rematrixing coefficients
bool _clipping = false; ///< Set to true if clipping detection is required int32_t _matrix32[CHANNELS_MAX][CHANNELS_MAX] = {
bool _valid = false; ///< Set to true if context is valid. {0}}; ///< 17.15 fixed point rematrixing coefficients
uint8_t _matrix_ch[CHANNELS_MAX][CHANNELS_MAX + 1] = {
{0}}; ///< Lists of input channels per output channel that have non zero
///< rematrixing coefficients
bool _clipping = false; ///< Set to true if clipping detection is required
bool _valid = false; ///< Set to true if context is valid.
}; };
int MixerContext::auto_matrix() int
MixerContext::auto_matrix()
{ {
double matrix[NUM_NAMED_CHANNELS][NUM_NAMED_CHANNELS] = { { 0 } }; double matrix[NUM_NAMED_CHANNELS][NUM_NAMED_CHANNELS] = {{0}};
double maxcoef = 0; double maxcoef = 0;
float maxval; float maxval;
@ -239,8 +244,7 @@ int MixerContext::auto_matrix()
matrix[FRONT_LEFT][BACK_CENTER] += _surround_mix_level * M_SQRT1_2; matrix[FRONT_LEFT][BACK_CENTER] += _surround_mix_level * M_SQRT1_2;
matrix[FRONT_RIGHT][BACK_CENTER] += _surround_mix_level * M_SQRT1_2; matrix[FRONT_RIGHT][BACK_CENTER] += _surround_mix_level * M_SQRT1_2;
} else if (out_ch_layout & CHANNEL_FRONT_CENTER) { } else if (out_ch_layout & CHANNEL_FRONT_CENTER) {
matrix[FRONT_CENTER][BACK_CENTER] += matrix[FRONT_CENTER][BACK_CENTER] += _surround_mix_level * M_SQRT1_2;
_surround_mix_level * M_SQRT1_2;
} }
} }
if (unaccounted & CHANNEL_BACK_LEFT) { if (unaccounted & CHANNEL_BACK_LEFT) {
@ -321,7 +325,7 @@ int MixerContext::auto_matrix()
_matrix[out_i][in_i] = matrix[i][j]; _matrix[out_i][in_i] = matrix[i][j];
} else { } else {
_matrix[out_i][in_i] = _matrix[out_i][in_i] =
i == j && (in_ch_layout & out_ch_layout & (1U << i)); i == j && (in_ch_layout & out_ch_layout & (1U << i));
} }
sum += fabs(_matrix[out_i][in_i]); sum += fabs(_matrix[out_i][in_i]);
in_i++; in_i++;
@ -356,7 +360,8 @@ int MixerContext::auto_matrix()
return 0; return 0;
} }
int MixerContext::init() int
MixerContext::init()
{ {
int r = auto_matrix(); int r = auto_matrix();
if (r) { if (r) {
@ -398,22 +403,15 @@ int MixerContext::init()
return 0; return 0;
} }
template<typename TYPE_SAMPLE, typename TYPE_COEFF, typename F> template <typename TYPE_SAMPLE, typename TYPE_COEFF, typename F>
void void
sum2(TYPE_SAMPLE * out, sum2(TYPE_SAMPLE * out, uint32_t stride_out, const TYPE_SAMPLE * in1,
uint32_t stride_out, const TYPE_SAMPLE * in2, uint32_t stride_in, TYPE_COEFF coeff1,
const TYPE_SAMPLE * in1, TYPE_COEFF coeff2, F && operand, uint32_t frames)
const TYPE_SAMPLE * in2,
uint32_t stride_in,
TYPE_COEFF coeff1,
TYPE_COEFF coeff2,
F&& operand,
uint32_t frames)
{ {
static_assert( static_assert(
std::is_same<TYPE_COEFF, std::is_same<TYPE_COEFF, decltype(operand(coeff1))>::value,
typename std::result_of<F(TYPE_COEFF)>::type>::value, "function must return the same type as used by coeff1 and coeff2");
"function must return the same type as used by matrix_coeff");
for (uint32_t i = 0; i < frames; i++) { for (uint32_t i = 0; i < frames; i++) {
*out = operand(coeff1 * *in1 + coeff2 * *in2); *out = operand(coeff1 * *in1 + coeff2 * *in2);
out += stride_out; out += stride_out;
@ -422,20 +420,13 @@ sum2(TYPE_SAMPLE * out,
} }
} }
template<typename TYPE_SAMPLE, typename TYPE_COEFF, typename F> template <typename TYPE_SAMPLE, typename TYPE_COEFF, typename F>
void void
copy(TYPE_SAMPLE * out, copy(TYPE_SAMPLE * out, uint32_t stride_out, const TYPE_SAMPLE * in,
uint32_t stride_out, uint32_t stride_in, TYPE_COEFF coeff, F && operand, uint32_t frames)
const TYPE_SAMPLE * in,
uint32_t stride_in,
TYPE_COEFF coeff,
F&& operand,
uint32_t frames)
{ {
static_assert( static_assert(std::is_same<TYPE_COEFF, decltype(operand(coeff))>::value,
std::is_same<TYPE_COEFF, "function must return the same type as used by coeff");
typename std::result_of<F(TYPE_COEFF)>::type>::value,
"function must return the same type as used by matrix_coeff");
for (uint32_t i = 0; i < frames; i++) { for (uint32_t i = 0; i < frames; i++) {
*out = operand(coeff * *in); *out = operand(coeff * *in);
out += stride_out; out += stride_out;
@ -444,74 +435,58 @@ copy(TYPE_SAMPLE * out,
} }
template <typename TYPE, typename TYPE_COEFF, size_t COLS, typename F> template <typename TYPE, typename TYPE_COEFF, size_t COLS, typename F>
static int rematrix(const MixerContext * s, TYPE * aOut, const TYPE * aIn, static int
const TYPE_COEFF (&matrix_coeff)[COLS][COLS], rematrix(const MixerContext * s, TYPE * aOut, const TYPE * aIn,
F&& aF, uint32_t frames) const TYPE_COEFF (&matrix_coeff)[COLS][COLS], F && aF, uint32_t frames)
{ {
static_assert( static_assert(
std::is_same<TYPE_COEFF, std::is_same<TYPE_COEFF, decltype(aF(matrix_coeff[0][0]))>::value,
typename std::result_of<F(TYPE_COEFF)>::type>::value, "function must return the same type as used by matrix_coeff");
"function must return the same type as used by matrix_coeff");
for (uint32_t out_i = 0; out_i < s->_out_ch_count; out_i++) { for (uint32_t out_i = 0; out_i < s->_out_ch_count; out_i++) {
TYPE* out = aOut + out_i; TYPE * out = aOut + out_i;
switch (s->_matrix_ch[out_i][0]) { switch (s->_matrix_ch[out_i][0]) {
case 0: case 0:
for (uint32_t i = 0; i < frames; i++) { for (uint32_t i = 0; i < frames; i++) {
out[i * s->_out_ch_count] = 0; out[i * s->_out_ch_count] = 0;
}
break;
case 1: {
int in_i = s->_matrix_ch[out_i][1];
copy(out, s->_out_ch_count, aIn + in_i, s->_in_ch_count,
matrix_coeff[out_i][in_i], aF, frames);
} break;
case 2:
sum2(out, s->_out_ch_count, aIn + s->_matrix_ch[out_i][1],
aIn + s->_matrix_ch[out_i][2], s->_in_ch_count,
matrix_coeff[out_i][s->_matrix_ch[out_i][1]],
matrix_coeff[out_i][s->_matrix_ch[out_i][2]], aF, frames);
break;
default:
for (uint32_t i = 0; i < frames; i++) {
TYPE_COEFF v = 0;
for (uint32_t j = 0; j < s->_matrix_ch[out_i][0]; j++) {
uint32_t in_i = s->_matrix_ch[out_i][1 + j];
v += *(aIn + in_i + i * s->_in_ch_count) * matrix_coeff[out_i][in_i];
} }
break; out[i * s->_out_ch_count] = aF(v);
case 1: { }
int in_i = s->_matrix_ch[out_i][1]; break;
copy(out,
s->_out_ch_count,
aIn + in_i,
s->_in_ch_count,
matrix_coeff[out_i][in_i],
aF,
frames);
} break;
case 2:
sum2(out,
s->_out_ch_count,
aIn + s->_matrix_ch[out_i][1],
aIn + s->_matrix_ch[out_i][2],
s->_in_ch_count,
matrix_coeff[out_i][s->_matrix_ch[out_i][1]],
matrix_coeff[out_i][s->_matrix_ch[out_i][2]],
aF,
frames);
break;
default:
for (uint32_t i = 0; i < frames; i++) {
TYPE_COEFF v = 0;
for (uint32_t j = 0; j < s->_matrix_ch[out_i][0]; j++) {
uint32_t in_i = s->_matrix_ch[out_i][1 + j];
v +=
*(aIn + in_i + i * s->_in_ch_count) * matrix_coeff[out_i][in_i];
}
out[i * s->_out_ch_count] = aF(v);
}
break;
} }
} }
return 0; return 0;
} }
struct cubeb_mixer struct cubeb_mixer {
{ cubeb_mixer(cubeb_sample_format format, uint32_t in_channels,
cubeb_mixer(cubeb_sample_format format, cubeb_channel_layout in_layout, uint32_t out_channels,
uint32_t in_channels,
cubeb_channel_layout in_layout,
uint32_t out_channels,
cubeb_channel_layout out_layout) cubeb_channel_layout out_layout)
: _context(format, in_channels, in_layout, out_channels, out_layout) : _context(format, in_channels, in_layout, out_channels, out_layout)
{ {
} }
template<typename T> template <typename T>
void copy_and_trunc(size_t frames, void copy_and_trunc(size_t frames, const T * input_buffer,
const T * input_buffer,
T * output_buffer) const T * output_buffer) const
{ {
if (_context._in_ch_count <= _context._out_ch_count) { if (_context._in_ch_count <= _context._out_ch_count) {
@ -545,11 +520,8 @@ struct cubeb_mixer
} }
} }
int mix(size_t frames, int mix(size_t frames, const void * input_buffer, size_t input_buffer_size,
const void * input_buffer, void * output_buffer, size_t output_buffer_size) const
size_t input_buffer_size,
void * output_buffer,
size_t output_buffer_size) const
{ {
if (frames <= 0 || _context._out_ch_count == 0) { if (frames <= 0 || _context._out_ch_count == 0) {
return 0; return 0;
@ -557,7 +529,7 @@ struct cubeb_mixer
// Check if output buffer is of sufficient size. // Check if output buffer is of sufficient size.
size_t size_read_needed = size_t size_read_needed =
frames * _context._in_ch_count * cubeb_sample_size(_context._format); frames * _context._in_ch_count * cubeb_sample_size(_context._format);
if (input_buffer_size < size_read_needed) { if (input_buffer_size < size_read_needed) {
// We don't have enough data to read! // We don't have enough data to read!
return -1; return -1;
@ -571,58 +543,46 @@ struct cubeb_mixer
// The channel layouts were invalid or unsupported, instead we will simply // The channel layouts were invalid or unsupported, instead we will simply
// either drop the extra channels, or fill with silence the missing ones // either drop the extra channels, or fill with silence the missing ones
if (_context._format == CUBEB_SAMPLE_FLOAT32NE) { if (_context._format == CUBEB_SAMPLE_FLOAT32NE) {
copy_and_trunc(frames, copy_and_trunc(frames, static_cast<const float *>(input_buffer),
static_cast<const float*>(input_buffer), static_cast<float *>(output_buffer));
static_cast<float*>(output_buffer));
} else { } else {
assert(_context._format == CUBEB_SAMPLE_S16NE); assert(_context._format == CUBEB_SAMPLE_S16NE);
copy_and_trunc(frames, copy_and_trunc(frames, static_cast<const int16_t *>(input_buffer),
static_cast<const int16_t*>(input_buffer), reinterpret_cast<int16_t *>(output_buffer));
reinterpret_cast<int16_t*>(output_buffer));
} }
return 0; return 0;
} }
switch (_context._format) switch (_context._format) {
{ case CUBEB_SAMPLE_FLOAT32NE: {
case CUBEB_SAMPLE_FLOAT32NE: { auto f = [](float x) { return x; };
auto f = [](float x) { return x; }; return rematrix(&_context, static_cast<float *>(output_buffer),
return rematrix(&_context, static_cast<const float *>(input_buffer),
static_cast<float*>(output_buffer), _context._matrix_flt, f, frames);
static_cast<const float*>(input_buffer), }
_context._matrix_flt, case CUBEB_SAMPLE_S16NE:
f, if (_context._clipping) {
frames); auto f = [](int x) {
int y = (x + 16384) >> 15;
// clip the signed integer value into the -32768,32767 range.
if ((y + 0x8000U) & ~0xFFFF) {
return (y >> 31) ^ 0x7FFF;
}
return y;
};
return rematrix(&_context, static_cast<int16_t *>(output_buffer),
static_cast<const int16_t *>(input_buffer),
_context._matrix32, f, frames);
} else {
auto f = [](int x) { return (x + 16384) >> 15; };
return rematrix(&_context, static_cast<int16_t *>(output_buffer),
static_cast<const int16_t *>(input_buffer),
_context._matrix32, f, frames);
} }
case CUBEB_SAMPLE_S16NE: break;
if (_context._clipping) { default:
auto f = [](int x) { assert(false);
int y = (x + 16384) >> 15; break;
// clip the signed integer value into the -32768,32767 range.
if ((y + 0x8000U) & ~0xFFFF) {
return (y >> 31) ^ 0x7FFF;
}
return y;
};
return rematrix(&_context,
static_cast<int16_t*>(output_buffer),
static_cast<const int16_t*>(input_buffer),
_context._matrix32,
f,
frames);
} else {
auto f = [](int x) { return (x + 16384) >> 15; };
return rematrix(&_context,
static_cast<int16_t*>(output_buffer),
static_cast<const int16_t*>(input_buffer),
_context._matrix32,
f,
frames);
}
break;
default:
assert(false);
break;
} }
return -1; return -1;
@ -636,28 +596,26 @@ struct cubeb_mixer
MixerContext _context; MixerContext _context;
}; };
cubeb_mixer* cubeb_mixer_create(cubeb_sample_format format, cubeb_mixer *
uint32_t in_channels, cubeb_mixer_create(cubeb_sample_format format, uint32_t in_channels,
cubeb_channel_layout in_layout, cubeb_channel_layout in_layout, uint32_t out_channels,
uint32_t out_channels, cubeb_channel_layout out_layout)
cubeb_channel_layout out_layout)
{ {
return new cubeb_mixer( return new cubeb_mixer(format, in_channels, in_layout, out_channels,
format, in_channels, in_layout, out_channels, out_layout); out_layout);
} }
void cubeb_mixer_destroy(cubeb_mixer * mixer) void
cubeb_mixer_destroy(cubeb_mixer * mixer)
{ {
delete mixer; delete mixer;
} }
int cubeb_mixer_mix(cubeb_mixer * mixer, int
size_t frames, cubeb_mixer_mix(cubeb_mixer * mixer, size_t frames, const void * input_buffer,
const void * input_buffer, size_t input_buffer_size, void * output_buffer,
size_t input_buffer_size, size_t output_buffer_size)
void * output_buffer,
size_t output_buffer_size)
{ {
return mixer->mix( return mixer->mix(frames, input_buffer, input_buffer_size, output_buffer,
frames, input_buffer, input_buffer_size, output_buffer, output_buffer_size); output_buffer_size);
} }

View file

@ -15,20 +15,19 @@ extern "C" {
#endif #endif
typedef struct cubeb_mixer cubeb_mixer; typedef struct cubeb_mixer cubeb_mixer;
cubeb_mixer * cubeb_mixer_create(cubeb_sample_format format, cubeb_mixer *
uint32_t in_channels, cubeb_mixer_create(cubeb_sample_format format, uint32_t in_channels,
cubeb_channel_layout in_layout, cubeb_channel_layout in_layout, uint32_t out_channels,
uint32_t out_channels, cubeb_channel_layout out_layout);
cubeb_channel_layout out_layout); void
void cubeb_mixer_destroy(cubeb_mixer * mixer); cubeb_mixer_destroy(cubeb_mixer * mixer);
int cubeb_mixer_mix(cubeb_mixer * mixer, int
size_t frames, cubeb_mixer_mix(cubeb_mixer * mixer, size_t frames, const void * input_buffer,
const void * input_buffer, size_t input_buffer_size, void * output_buffer,
size_t input_buffer_size, size_t output_buffer_size);
void * output_buffer,
size_t output_buffer_size);
unsigned int cubeb_channel_layout_nb_channels(cubeb_channel_layout channel_layout); unsigned int
cubeb_channel_layout_nb_channels(cubeb_channel_layout channel_layout);
#if defined(__cplusplus) #if defined(__cplusplus)
} }

File diff suppressed because it is too large Load diff

View file

@ -10,25 +10,25 @@
* accompanying file LICENSE for details. * accompanying file LICENSE for details.
*/ */
#include <assert.h> #include "cubeb-internal.h"
#include <ctype.h>
#include <limits.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/soundcard.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <unistd.h>
#include <pthread.h>
#include <stdbool.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <poll.h>
#include "cubeb/cubeb.h" #include "cubeb/cubeb.h"
#include "cubeb_mixer.h" #include "cubeb_mixer.h"
#include "cubeb_strings.h" #include "cubeb_strings.h"
#include "cubeb-internal.h" #include <assert.h>
#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <poll.h>
#include <pthread.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/soundcard.h>
#include <sys/types.h>
#include <unistd.h>
/* Supported well by most hardware. */ /* Supported well by most hardware. */
#ifndef OSS_PREFER_RATE #ifndef OSS_PREFER_RATE
@ -55,25 +55,25 @@
#define ENV_AUDIO_DEVICE "AUDIO_DEVICE" #define ENV_AUDIO_DEVICE "AUDIO_DEVICE"
#ifndef OSS_MAX_CHANNELS #ifndef OSS_MAX_CHANNELS
# if defined(__FreeBSD__) || defined(__DragonFly__) #if defined(__FreeBSD__) || defined(__DragonFly__)
/* /*
* The current maximum number of channels supported * The current maximum number of channels supported
* on FreeBSD is 8. * on FreeBSD is 8.
* *
* Reference: FreeBSD 12.1-RELEASE * Reference: FreeBSD 12.1-RELEASE
*/ */
# define OSS_MAX_CHANNELS (8) #define OSS_MAX_CHANNELS (8)
# elif defined(__sun__) #elif defined(__sun__)
/* /*
* The current maximum number of channels supported * The current maximum number of channels supported
* on Illumos is 16. * on Illumos is 16.
* *
* Reference: PSARC 2008/318 * Reference: PSARC 2008/318
*/ */
# define OSS_MAX_CHANNELS (16) #define OSS_MAX_CHANNELS (16)
# else #else
# define OSS_MAX_CHANNELS (2) #define OSS_MAX_CHANNELS (2)
# endif #endif
#endif #endif
#if defined(__FreeBSD__) || defined(__DragonFly__) #if defined(__FreeBSD__) || defined(__DragonFly__)
@ -89,13 +89,16 @@ struct cubeb {
/* Our intern string store */ /* Our intern string store */
pthread_mutex_t mutex; /* protects devid_strs */ pthread_mutex_t mutex; /* protects devid_strs */
cubeb_strings *devid_strs; cubeb_strings * devid_strs;
}; };
struct oss_stream { struct oss_stream {
oss_devnode_t name; oss_devnode_t name;
int fd; int fd;
void * buf; void * buf;
unsigned int nfr; /* Number of frames allocated */
unsigned int nfrags;
unsigned int bufframes;
struct stream_info { struct stream_info {
int channels; int channels;
@ -112,29 +115,26 @@ struct cubeb_stream {
struct cubeb * context; struct cubeb * context;
void * user_ptr; void * user_ptr;
pthread_t thread; pthread_t thread;
bool doorbell; /* (m) */ bool doorbell; /* (m) */
pthread_cond_t doorbell_cv; /* (m) */ pthread_cond_t doorbell_cv; /* (m) */
pthread_cond_t stopped_cv; /* (m) */ pthread_cond_t stopped_cv; /* (m) */
pthread_mutex_t mtx; /* Members protected by this should be marked (m) */ pthread_mutex_t mtx; /* Members protected by this should be marked (m) */
bool thread_created; /* (m) */ bool thread_created; /* (m) */
bool running; /* (m) */ bool running; /* (m) */
bool destroying; /* (m) */ bool destroying; /* (m) */
cubeb_state state; /* (m) */ cubeb_state state; /* (m) */
float volume /* (m) */; float volume /* (m) */;
struct oss_stream play; struct oss_stream play;
struct oss_stream record; struct oss_stream record;
cubeb_data_callback data_cb; cubeb_data_callback data_cb;
cubeb_state_callback state_cb; cubeb_state_callback state_cb;
uint64_t frames_written /* (m) */; uint64_t frames_written /* (m) */;
unsigned int nfr; /* Number of frames allocated */
unsigned int nfrags;
unsigned int bufframes;
}; };
static char const * static char const *
oss_cubeb_devid_intern(cubeb *context, char const * devid) oss_cubeb_devid_intern(cubeb * context, char const * devid)
{ {
char const *is; char const * is;
pthread_mutex_lock(&context->mutex); pthread_mutex_lock(&context->mutex);
is = cubeb_strings_intern(context->devid_strs, devid); is = cubeb_strings_intern(context->devid_strs, devid);
pthread_mutex_unlock(&context->mutex); pthread_mutex_unlock(&context->mutex);
@ -142,7 +142,8 @@ oss_cubeb_devid_intern(cubeb *context, char const * devid)
} }
int int
oss_init(cubeb **context, char const *context_name) { oss_init(cubeb ** context, char const * context_name)
{
cubeb * c; cubeb * c;
(void)context_name; (void)context_name;
@ -211,7 +212,7 @@ oss_get_min_latency(cubeb * context, cubeb_stream_params params,
} }
static void static void
oss_free_cubeb_device_info_strings(cubeb_device_info *cdi) oss_free_cubeb_device_info_strings(cubeb_device_info * cdi)
{ {
free((char *)cdi->device_id); free((char *)cdi->device_id);
free((char *)cdi->friendly_name); free((char *)cdi->friendly_name);
@ -230,8 +231,8 @@ oss_free_cubeb_device_info_strings(cubeb_device_info *cdi)
* Return 0 if OK, otherwise 1. * Return 0 if OK, otherwise 1.
*/ */
static int static int
oss_probe_open(const char *dsppath, cubeb_device_type type, oss_probe_open(const char * dsppath, cubeb_device_type type, int * fdp,
int *fdp, oss_audioinfo *resai) oss_audioinfo * resai)
{ {
oss_audioinfo ai; oss_audioinfo ai;
int error; int error;
@ -258,81 +259,81 @@ oss_probe_open(const char *dsppath, cubeb_device_type type,
struct sndstat_info { struct sndstat_info {
oss_devnode_t devname; oss_devnode_t devname;
const char *desc; const char * desc;
cubeb_device_type type; cubeb_device_type type;
int preferred; int preferred;
}; };
static int static int
oss_sndstat_line_parse(char *line, int is_ud, struct sndstat_info *sinfo) oss_sndstat_line_parse(char * line, int is_ud, struct sndstat_info * sinfo)
{ {
char *matchptr = line, *n = NULL; char *matchptr = line, *n = NULL;
struct sndstat_info res; struct sndstat_info res;
memset(&res, 0, sizeof(res)); memset(&res, 0, sizeof(res));
n = strchr(matchptr, ':'); n = strchr(matchptr, ':');
if (n == NULL) if (n == NULL)
goto fail;
if (is_ud == 0) {
unsigned int devunit;
if (sscanf(matchptr, "pcm%u: ", &devunit) < 1)
goto fail; goto fail;
if (is_ud == 0) {
unsigned int devunit;
if (sscanf(matchptr, "pcm%u: ", &devunit) < 1) if (snprintf(res.devname, sizeof(res.devname), "/dev/dsp%u", devunit) < 1)
goto fail;
if (snprintf(res.devname, sizeof(res.devname), "/dev/dsp%u", devunit) < 1)
goto fail;
} else {
if (n - matchptr >= (ssize_t)(sizeof(res.devname) - strlen("/dev/")))
goto fail;
strlcpy(res.devname, "/dev/", sizeof(res.devname));
strncat(res.devname, matchptr, n - matchptr);
}
matchptr = n + 1;
n = strchr(matchptr, '<');
if (n == NULL)
goto fail; goto fail;
matchptr = n + 1; } else {
n = strrchr(matchptr, '>'); if (n - matchptr >= (ssize_t)(sizeof(res.devname) - strlen("/dev/")))
if (n == NULL)
goto fail; goto fail;
*n = 0;
res.desc = matchptr;
matchptr = n + 1;
n = strchr(matchptr, '('); strlcpy(res.devname, "/dev/", sizeof(res.devname));
if (n == NULL) strncat(res.devname, matchptr, n - matchptr);
goto fail; }
matchptr = n + 1; matchptr = n + 1;
n = strrchr(matchptr, ')');
if (n == NULL)
goto fail;
*n = 0;
if (!isdigit(matchptr[0])) {
if (strstr(matchptr, "play") != NULL)
res.type |= CUBEB_DEVICE_TYPE_OUTPUT;
if (strstr(matchptr, "rec") != NULL)
res.type |= CUBEB_DEVICE_TYPE_INPUT;
} else {
int p, r;
if (sscanf(matchptr, "%dp:%*dv/%dr:%*dv", &p, &r) != 2)
goto fail;
if (p > 0)
res.type |= CUBEB_DEVICE_TYPE_OUTPUT;
if (r > 0)
res.type |= CUBEB_DEVICE_TYPE_INPUT;
}
matchptr = n + 1;
if (strstr(matchptr, "default") != NULL)
res.preferred = 1;
*sinfo = res; n = strchr(matchptr, '<');
return 0; if (n == NULL)
goto fail;
matchptr = n + 1;
n = strrchr(matchptr, '>');
if (n == NULL)
goto fail;
*n = 0;
res.desc = matchptr;
matchptr = n + 1;
n = strchr(matchptr, '(');
if (n == NULL)
goto fail;
matchptr = n + 1;
n = strrchr(matchptr, ')');
if (n == NULL)
goto fail;
*n = 0;
if (!isdigit(matchptr[0])) {
if (strstr(matchptr, "play") != NULL)
res.type |= CUBEB_DEVICE_TYPE_OUTPUT;
if (strstr(matchptr, "rec") != NULL)
res.type |= CUBEB_DEVICE_TYPE_INPUT;
} else {
int p, r;
if (sscanf(matchptr, "%dp:%*dv/%dr:%*dv", &p, &r) != 2)
goto fail;
if (p > 0)
res.type |= CUBEB_DEVICE_TYPE_OUTPUT;
if (r > 0)
res.type |= CUBEB_DEVICE_TYPE_INPUT;
}
matchptr = n + 1;
if (strstr(matchptr, "default") != NULL)
res.preferred = 1;
*sinfo = res;
return 0;
fail: fail:
return 1; return 1;
} }
/* /*
@ -344,10 +345,10 @@ static int
oss_enumerate_devices(cubeb * context, cubeb_device_type type, oss_enumerate_devices(cubeb * context, cubeb_device_type type,
cubeb_device_collection * collection) cubeb_device_collection * collection)
{ {
cubeb_device_info *devinfop = NULL; cubeb_device_info * devinfop = NULL;
char *line = NULL; char * line = NULL;
size_t linecap = 0; size_t linecap = 0;
FILE *sndstatfp = NULL; FILE * sndstatfp = NULL;
int collection_cnt = 0; int collection_cnt = 0;
int is_ud = 0; int is_ud = 0;
int skipall = 0; int skipall = 0;
@ -360,7 +361,7 @@ oss_enumerate_devices(cubeb * context, cubeb_device_type type,
if (sndstatfp == NULL) if (sndstatfp == NULL)
goto fail; goto fail;
while (getline(&line, &linecap, sndstatfp) > 0) { while (getline(&line, &linecap, sndstatfp) > 0) {
const char *devid = NULL; const char * devid = NULL;
struct sndstat_info sinfo; struct sndstat_info sinfo;
oss_audioinfo ai; oss_audioinfo ai;
@ -373,7 +374,8 @@ oss_enumerate_devices(cubeb * context, cubeb_device_type type,
skipall = 0; skipall = 0;
continue; continue;
} }
if (!strncmp(line, SNDSTAT_USER_BEGIN_STR, strlen(SNDSTAT_USER_BEGIN_STR))) { if (!strncmp(line, SNDSTAT_USER_BEGIN_STR,
strlen(SNDSTAT_USER_BEGIN_STR))) {
is_ud = 1; is_ud = 1;
skipall = 0; skipall = 0;
continue; continue;
@ -433,8 +435,8 @@ oss_enumerate_devices(cubeb * context, cubeb_device_type type,
collection_cnt++; collection_cnt++;
void *newp = reallocarray(devinfop, collection_cnt + 1, void * newp =
sizeof(cubeb_device_info)); reallocarray(devinfop, collection_cnt + 1, sizeof(cubeb_device_info));
if (newp == NULL) if (newp == NULL)
goto fail; goto fail;
devinfop = newp; devinfop = newp;
@ -464,7 +466,7 @@ oss_enumerate_devices(cubeb * context, cubeb_device_type type,
{ {
oss_sysinfo si; oss_sysinfo si;
int error, i; int error, i;
cubeb_device_info *devinfop = NULL; cubeb_device_info * devinfop = NULL;
int collection_cnt = 0; int collection_cnt = 0;
int mixer_fd = -1; int mixer_fd = -1;
@ -476,7 +478,8 @@ oss_enumerate_devices(cubeb * context, cubeb_device_type type,
error = ioctl(mixer_fd, SNDCTL_SYSINFO, &si); error = ioctl(mixer_fd, SNDCTL_SYSINFO, &si);
if (error) { if (error) {
LOG("Failed to run SNDCTL_SYSINFO on mixer %s. errno: %d", OSS_DEFAULT_MIXER, errno); LOG("Failed to run SNDCTL_SYSINFO on mixer %s. errno: %d",
OSS_DEFAULT_MIXER, errno);
goto fail; goto fail;
} }
@ -487,8 +490,8 @@ oss_enumerate_devices(cubeb * context, cubeb_device_type type,
collection->count = 0; collection->count = 0;
for (i = 0; i < si.numaudios; i++) { for (i = 0; i < si.numaudios; i++) {
oss_audioinfo ai; oss_audioinfo ai;
cubeb_device_info cdi = { 0 }; cubeb_device_info cdi = {0};
const char *devid = NULL; const char * devid = NULL;
ai.dev = i; ai.dev = i;
error = ioctl(mixer_fd, SNDCTL_AUDIOINFO, &ai); error = ioctl(mixer_fd, SNDCTL_AUDIOINFO, &ai);
@ -574,24 +577,24 @@ static unsigned int
oss_chn_from_cubeb(cubeb_channel chn) oss_chn_from_cubeb(cubeb_channel chn)
{ {
switch (chn) { switch (chn) {
case CHANNEL_FRONT_LEFT: case CHANNEL_FRONT_LEFT:
return CHID_L; return CHID_L;
case CHANNEL_FRONT_RIGHT: case CHANNEL_FRONT_RIGHT:
return CHID_R; return CHID_R;
case CHANNEL_FRONT_CENTER: case CHANNEL_FRONT_CENTER:
return CHID_C; return CHID_C;
case CHANNEL_LOW_FREQUENCY: case CHANNEL_LOW_FREQUENCY:
return CHID_LFE; return CHID_LFE;
case CHANNEL_BACK_LEFT: case CHANNEL_BACK_LEFT:
return CHID_LR; return CHID_LR;
case CHANNEL_BACK_RIGHT: case CHANNEL_BACK_RIGHT:
return CHID_RR; return CHID_RR;
case CHANNEL_SIDE_LEFT: case CHANNEL_SIDE_LEFT:
return CHID_LS; return CHID_LS;
case CHANNEL_SIDE_RIGHT: case CHANNEL_SIDE_RIGHT:
return CHID_RS; return CHID_RS;
default: default:
return CHID_UNDEF; return CHID_UNDEF;
} }
} }
@ -648,7 +651,8 @@ oss_copy_params(int fd, cubeb_stream * stream, cubeb_stream_params * params,
return CUBEB_ERROR; return CUBEB_ERROR;
} }
/* Mono layout is an exception */ /* Mono layout is an exception */
if (params->layout != CUBEB_LAYOUT_UNDEFINED && params->layout != CUBEB_LAYOUT_MONO) { if (params->layout != CUBEB_LAYOUT_UNDEFINED &&
params->layout != CUBEB_LAYOUT_MONO) {
chnorder = oss_cubeb_layout_to_chnorder(params->layout); chnorder = oss_cubeb_layout_to_chnorder(params->layout);
if (ioctl(fd, SNDCTL_DSP_SET_CHNORDER, &chnorder) == -1) if (ioctl(fd, SNDCTL_DSP_SET_CHNORDER, &chnorder) == -1)
LOG("Non-fatal error %d occured when setting channel order.", errno); LOG("Non-fatal error %d occured when setting channel order.", errno);
@ -741,73 +745,191 @@ oss_linear16_set_vol(int16_t * buf, unsigned sample_count, float vol)
} }
} }
static int
oss_get_rec_frames(cubeb_stream * s, unsigned int nframes)
{
size_t rem = nframes * s->record.frame_size;
size_t read_ofs = 0;
while (rem > 0) {
ssize_t n;
if ((n = read(s->record.fd, (uint8_t *)s->record.buf + read_ofs, rem)) <
0) {
if (errno == EINTR)
continue;
return CUBEB_ERROR;
}
read_ofs += n;
rem -= n;
}
return 0;
}
static int
oss_put_play_frames(cubeb_stream * s, unsigned int nframes)
{
size_t rem = nframes * s->play.frame_size;
size_t write_ofs = 0;
while (rem > 0) {
ssize_t n;
if ((n = write(s->play.fd, (uint8_t *)s->play.buf + write_ofs, rem)) < 0) {
if (errno == EINTR)
continue;
return CUBEB_ERROR;
}
pthread_mutex_lock(&s->mtx);
s->frames_written += n / s->play.frame_size;
pthread_mutex_unlock(&s->mtx);
write_ofs += n;
rem -= n;
}
return 0;
}
static int
oss_wait_fds_for_space(cubeb_stream * s, long * nfrp)
{
audio_buf_info bi;
struct pollfd pfds[2];
long nfr, tnfr;
int i;
assert(s->play.fd != -1 || s->record.fd != -1);
pfds[0].events = POLLOUT | POLLHUP;
pfds[0].revents = 0;
pfds[0].fd = s->play.fd;
pfds[1].events = POLLIN | POLLHUP;
pfds[1].revents = 0;
pfds[1].fd = s->record.fd;
retry:
nfr = LONG_MAX;
if (poll(pfds, 2, 1000) == -1) {
return CUBEB_ERROR;
}
for (i = 0; i < 2; i++) {
if (pfds[i].revents & POLLHUP) {
return CUBEB_ERROR;
}
}
if (s->play.fd != -1) {
if (ioctl(s->play.fd, SNDCTL_DSP_GETOSPACE, &bi) == -1) {
return CUBEB_STATE_ERROR;
}
tnfr = bi.bytes / s->play.frame_size;
if (tnfr <= 0) {
/* too little space - stop polling record, if any */
pfds[0].fd = s->play.fd;
pfds[1].fd = -1;
goto retry;
} else if (tnfr > (long)s->play.bufframes) {
/* too many frames available - limit */
tnfr = (long)s->play.bufframes;
}
if (nfr > tnfr) {
nfr = tnfr;
}
}
if (s->record.fd != -1) {
if (ioctl(s->record.fd, SNDCTL_DSP_GETISPACE, &bi) == -1) {
return CUBEB_STATE_ERROR;
}
tnfr = bi.bytes / s->record.frame_size;
if (tnfr <= 0) {
/* too little space - stop polling playback, if any */
pfds[0].fd = -1;
pfds[1].fd = s->record.fd;
goto retry;
} else if (tnfr > (long)s->record.bufframes) {
/* too many frames available - limit */
tnfr = (long)s->record.bufframes;
}
if (nfr > tnfr) {
nfr = tnfr;
}
}
*nfrp = nfr;
return 0;
}
/* 1 - Stopped by cubeb_stream_stop, otherwise 0 */ /* 1 - Stopped by cubeb_stream_stop, otherwise 0 */
static int static int
oss_audio_loop(cubeb_stream * s, cubeb_state *new_state) oss_audio_loop(cubeb_stream * s, cubeb_state * new_state)
{ {
cubeb_state state = CUBEB_STATE_STOPPED; cubeb_state state = CUBEB_STATE_STOPPED;
int trig = 0; int trig = 0, drain = 0;
int drain = 0; const bool play_on = s->play.fd != -1, record_on = s->record.fd != -1;
struct pollfd pfds[2]; long nfr = 0;
unsigned int ppending, rpending;
pfds[0].fd = s->play.fd; if (record_on) {
pfds[0].events = POLLOUT;
pfds[1].fd = s->record.fd;
pfds[1].events = POLLIN;
ppending = 0;
rpending = s->bufframes;
if (s->record.fd != -1) {
if (ioctl(s->record.fd, SNDCTL_DSP_SETTRIGGER, &trig)) { if (ioctl(s->record.fd, SNDCTL_DSP_SETTRIGGER, &trig)) {
LOG("Error %d occured when setting trigger on record fd", errno); LOG("Error %d occured when setting trigger on record fd", errno);
state = CUBEB_STATE_ERROR; state = CUBEB_STATE_ERROR;
goto breakdown; goto breakdown;
} }
trig |= PCM_ENABLE_INPUT; trig |= PCM_ENABLE_INPUT;
if (ioctl(s->record.fd, SNDCTL_DSP_SETTRIGGER, &trig)) { memset(s->record.buf, 0, s->record.bufframes * s->record.frame_size);
if (ioctl(s->record.fd, SNDCTL_DSP_SETTRIGGER, &trig) == -1) {
LOG("Error %d occured when setting trigger on record fd", errno); LOG("Error %d occured when setting trigger on record fd", errno);
state = CUBEB_STATE_ERROR; state = CUBEB_STATE_ERROR;
goto breakdown; goto breakdown;
} }
memset(s->record.buf, 0, s->bufframes * s->record.frame_size); }
if (!play_on && !record_on) {
/*
* Stop here if the stream is not play & record stream,
* play-only stream or record-only stream
*/
goto breakdown;
} }
while (1) { while (1) {
long nfr = 0;
pthread_mutex_lock(&s->mtx); pthread_mutex_lock(&s->mtx);
if (!s->running || s->destroying) { if (!s->running || s->destroying) {
pthread_mutex_unlock(&s->mtx); pthread_mutex_unlock(&s->mtx);
break; break;
} }
pthread_mutex_unlock(&s->mtx); pthread_mutex_unlock(&s->mtx);
if (s->play.fd == -1 && s->record.fd == -1) {
/*
* Stop here if the stream is not play & record stream,
* play-only stream or record-only stream
*/
goto breakdown; long got = 0;
} if (nfr > 0) {
if (record_on) {
while ((s->bufframes - ppending) >= s->nfr && rpending >= s->nfr) { if (oss_get_rec_frames(s, nfr) == CUBEB_ERROR) {
long n = ((s->bufframes - ppending) < rpending) ? s->bufframes - ppending : rpending; state = CUBEB_STATE_ERROR;
char *rptr = NULL, *pptr = NULL; goto breakdown;
if (s->record.fd != -1) }
rptr = (char *)s->record.buf; if (s->record.floating) {
if (s->play.fd != -1) oss_linear32_to_float(s->record.buf, s->record.info.channels * nfr);
pptr = (char *)s->play.buf + ppending * s->play.frame_size; }
if (s->record.fd != -1 && s->record.floating) {
oss_linear32_to_float(s->record.buf, s->record.info.channels * n);
} }
nfr = s->data_cb(s, s->user_ptr, rptr, pptr, n);
if (nfr == CUBEB_ERROR) { got = s->data_cb(s, s->user_ptr, s->record.buf, s->play.buf, nfr);
if (got == CUBEB_ERROR) {
state = CUBEB_STATE_ERROR; state = CUBEB_STATE_ERROR;
goto breakdown; goto breakdown;
} }
if (pptr) { if (got < nfr) {
if (s->play.fd != -1) {
drain = 1;
} else {
/*
* This is a record-only stream and number of frames
* returned from data_cb() is smaller than number
* of frames required to read. Stop here.
*/
state = CUBEB_STATE_STOPPED;
goto breakdown;
}
}
if (got > 0 && play_on) {
float vol; float vol;
pthread_mutex_lock(&s->mtx); pthread_mutex_lock(&s->mtx);
@ -815,102 +937,24 @@ oss_audio_loop(cubeb_stream * s, cubeb_state *new_state)
pthread_mutex_unlock(&s->mtx); pthread_mutex_unlock(&s->mtx);
if (s->play.floating) { if (s->play.floating) {
oss_float_to_linear32(pptr, s->play.info.channels * nfr, vol); oss_float_to_linear32(s->play.buf, s->play.info.channels * got, vol);
} else { } else {
oss_linear16_set_vol((int16_t *)pptr, s->play.info.channels * nfr, vol); oss_linear16_set_vol((int16_t *)s->play.buf,
s->play.info.channels * got, vol);
} }
} if (oss_put_play_frames(s, got) == CUBEB_ERROR) {
if (pptr) {
ppending += nfr;
assert(ppending <= s->bufframes);
}
if (rptr) {
assert(rpending >= nfr);
rpending -= nfr;
memmove(rptr, rptr + nfr * s->record.frame_size,
(s->bufframes - nfr) * s->record.frame_size);
}
if (nfr < n) {
if (s->play.fd != -1) {
drain = 1;
break;
} else {
/*
* This is a record-only stream and number of frames
* returned from data_cb() is smaller than number
* of frames required to read. Stop here.
*/
state = CUBEB_STATE_STOPPED;
goto breakdown;
}
}
}
ssize_t n, frames;
int nfds;
pfds[0].revents = 0;
pfds[1].revents = 0;
nfds = poll(pfds, 2, 1000);
if (nfds == -1) {
if (errno == EINTR)
continue;
LOG("Error %d occured when polling playback and record fd", errno);
state = CUBEB_STATE_ERROR;
goto breakdown;
} else if (nfds == 0)
continue;
if ((pfds[0].revents & (POLLERR | POLLHUP)) ||
(pfds[1].revents & (POLLERR | POLLHUP))) {
LOG("Error occured on playback, record fds");
state = CUBEB_STATE_ERROR;
goto breakdown;
}
if (pfds[0].revents) {
while (ppending > 0) {
size_t bytes = ppending * s->play.frame_size;
if ((n = write(s->play.fd, (uint8_t *)s->play.buf, bytes)) < 0) {
if (errno == EINTR)
continue;
if (errno == EAGAIN) {
if (drain)
continue;
break;
}
state = CUBEB_STATE_ERROR; state = CUBEB_STATE_ERROR;
goto breakdown; goto breakdown;
} }
frames = n / s->play.frame_size; }
pthread_mutex_lock(&s->mtx); if (drain) {
s->frames_written += frames; state = CUBEB_STATE_DRAINED;
pthread_mutex_unlock(&s->mtx); goto breakdown;
ppending -= frames;
memmove(s->play.buf, (uint8_t *)s->play.buf + n,
(s->bufframes - frames) * s->play.frame_size);
} }
} }
if (pfds[1].revents) {
while (s->bufframes - rpending > 0) { if (oss_wait_fds_for_space(s, &nfr) != 0) {
size_t bytes = (s->bufframes - rpending) * s->record.frame_size; state = CUBEB_STATE_ERROR;
size_t read_ofs = rpending * s->record.frame_size;
if ((n = read(s->record.fd, (uint8_t *)s->record.buf + read_ofs, bytes)) < 0) {
if (errno == EINTR)
continue;
if (errno == EAGAIN)
break;
state = CUBEB_STATE_ERROR;
goto breakdown;
}
frames = n / s->record.frame_size;
rpending += frames;
}
}
if (drain) {
state = CUBEB_STATE_DRAINED;
goto breakdown; goto breakdown;
} }
} }
@ -926,9 +970,9 @@ breakdown:
} }
static void * static void *
oss_io_routine(void *arg) oss_io_routine(void * arg)
{ {
cubeb_stream *s = arg; cubeb_stream * s = arg;
cubeb_state new_state; cubeb_state new_state;
int stopped; int stopped;
@ -969,9 +1013,10 @@ static inline int
oss_calc_frag_shift(unsigned int frames, unsigned int frame_size) oss_calc_frag_shift(unsigned int frames, unsigned int frame_size)
{ {
int n = 4; int n = 4;
int blksize = (frames * frame_size + OSS_NFRAGS - 1) / OSS_NFRAGS; int blksize = frames * frame_size;
while ((1 << n) < blksize) while ((1 << n) < blksize) {
n++; n++;
}
return n; return n;
} }
@ -982,22 +1027,17 @@ oss_get_frag_params(unsigned int shift)
} }
static int static int
oss_stream_init(cubeb * context, oss_stream_init(cubeb * context, cubeb_stream ** stream,
cubeb_stream ** stream, char const * stream_name, cubeb_devid input_device,
char const * stream_name,
cubeb_devid input_device,
cubeb_stream_params * input_stream_params, cubeb_stream_params * input_stream_params,
cubeb_devid output_device, cubeb_devid output_device,
cubeb_stream_params * output_stream_params, cubeb_stream_params * output_stream_params,
unsigned int latency_frames, unsigned int latency_frames, cubeb_data_callback data_callback,
cubeb_data_callback data_callback, cubeb_state_callback state_callback, void * user_ptr)
cubeb_state_callback state_callback,
void * user_ptr)
{ {
int ret = CUBEB_OK; int ret = CUBEB_OK;
unsigned int playnfr = 0, recnfr = 0; cubeb_stream * s = NULL;
cubeb_stream *s = NULL; const char * defdsp;
const char *defdsp;
if (!(defdsp = getenv(ENV_AUDIO_DEVICE)) || *defdsp == '\0') if (!(defdsp = getenv(ENV_AUDIO_DEVICE)) || *defdsp == '\0')
defdsp = OSS_DEFAULT_DEVICE; defdsp = OSS_DEFAULT_DEVICE;
@ -1009,7 +1049,6 @@ oss_stream_init(cubeb * context,
} }
s->state = CUBEB_STATE_STOPPED; s->state = CUBEB_STATE_STOPPED;
s->record.fd = s->play.fd = -1; s->record.fd = s->play.fd = -1;
s->nfr = latency_frames;
if (input_device != NULL) { if (input_device != NULL) {
strlcpy(s->record.name, input_device, sizeof(s->record.name)); strlcpy(s->record.name, input_device, sizeof(s->record.name));
} else { } else {
@ -1030,26 +1069,35 @@ oss_stream_init(cubeb * context,
nb_channels = cubeb_channel_layout_nb_channels(input_stream_params->layout); nb_channels = cubeb_channel_layout_nb_channels(input_stream_params->layout);
if (input_stream_params->layout != CUBEB_LAYOUT_UNDEFINED && if (input_stream_params->layout != CUBEB_LAYOUT_UNDEFINED &&
nb_channels != input_stream_params->channels) { nb_channels != input_stream_params->channels) {
LOG("input_stream_params->layout does not match input_stream_params->channels"); LOG("input_stream_params->layout does not match "
"input_stream_params->channels");
ret = CUBEB_ERROR_INVALID_PARAMETER; ret = CUBEB_ERROR_INVALID_PARAMETER;
goto error; goto error;
} }
if (s->record.fd == -1) { if ((s->record.fd = open(s->record.name, O_RDONLY)) == -1) {
if ((s->record.fd = open(s->record.name, O_RDONLY | O_NONBLOCK)) == -1) { LOG("Audio device \"%s\" could not be opened as read-only",
LOG("Audio device \"%s\" could not be opened as read-only", s->record.name);
s->record.name); ret = CUBEB_ERROR_DEVICE_UNAVAILABLE;
ret = CUBEB_ERROR_DEVICE_UNAVAILABLE; goto error;
goto error;
}
} }
if ((ret = oss_copy_params(s->record.fd, s, input_stream_params, if ((ret = oss_copy_params(s->record.fd, s, input_stream_params,
&s->record.info)) != CUBEB_OK) { &s->record.info)) != CUBEB_OK) {
LOG("Setting record params failed"); LOG("Setting record params failed");
goto error; goto error;
} }
s->record.floating = (input_stream_params->format == CUBEB_SAMPLE_FLOAT32NE); s->record.floating =
s->record.frame_size = s->record.info.channels * (s->record.info.precision / 8); (input_stream_params->format == CUBEB_SAMPLE_FLOAT32NE);
recnfr = (1 << oss_calc_frag_shift(s->nfr, s->record.frame_size)) / s->record.frame_size; s->record.frame_size =
s->record.info.channels * (s->record.info.precision / 8);
s->record.nfrags = OSS_NFRAGS;
s->record.nfr = latency_frames / OSS_NFRAGS;
s->record.bufframes = s->record.nfrags * s->record.nfr;
uint32_t minnfr;
oss_get_min_latency(context, *input_stream_params, &minnfr);
if (s->record.nfr < minnfr) {
s->record.nfr = minnfr;
s->record.nfrags = latency_frames / minnfr;
}
} }
if (output_stream_params != NULL) { if (output_stream_params != NULL) {
unsigned int nb_channels; unsigned int nb_channels;
@ -1058,20 +1106,20 @@ oss_stream_init(cubeb * context,
ret = CUBEB_ERROR_NOT_SUPPORTED; ret = CUBEB_ERROR_NOT_SUPPORTED;
goto error; goto error;
} }
nb_channels = cubeb_channel_layout_nb_channels(output_stream_params->layout); nb_channels =
cubeb_channel_layout_nb_channels(output_stream_params->layout);
if (output_stream_params->layout != CUBEB_LAYOUT_UNDEFINED && if (output_stream_params->layout != CUBEB_LAYOUT_UNDEFINED &&
nb_channels != output_stream_params->channels) { nb_channels != output_stream_params->channels) {
LOG("output_stream_params->layout does not match output_stream_params->channels"); LOG("output_stream_params->layout does not match "
"output_stream_params->channels");
ret = CUBEB_ERROR_INVALID_PARAMETER; ret = CUBEB_ERROR_INVALID_PARAMETER;
goto error; goto error;
} }
if (s->play.fd == -1) { if ((s->play.fd = open(s->play.name, O_WRONLY)) == -1) {
if ((s->play.fd = open(s->play.name, O_WRONLY | O_NONBLOCK)) == -1) { LOG("Audio device \"%s\" could not be opened as write-only",
LOG("Audio device \"%s\" could not be opened as write-only", s->play.name);
s->play.name); ret = CUBEB_ERROR_DEVICE_UNAVAILABLE;
ret = CUBEB_ERROR_DEVICE_UNAVAILABLE; goto error;
goto error;
}
} }
if ((ret = oss_copy_params(s->play.fd, s, output_stream_params, if ((ret = oss_copy_params(s->play.fd, s, output_stream_params,
&s->play.info)) != CUBEB_OK) { &s->play.info)) != CUBEB_OK) {
@ -1080,23 +1128,55 @@ oss_stream_init(cubeb * context,
} }
s->play.floating = (output_stream_params->format == CUBEB_SAMPLE_FLOAT32NE); s->play.floating = (output_stream_params->format == CUBEB_SAMPLE_FLOAT32NE);
s->play.frame_size = s->play.info.channels * (s->play.info.precision / 8); s->play.frame_size = s->play.info.channels * (s->play.info.precision / 8);
playnfr = (1 << oss_calc_frag_shift(s->nfr, s->play.frame_size)) / s->play.frame_size; s->play.nfrags = OSS_NFRAGS;
s->play.nfr = latency_frames / OSS_NFRAGS;
uint32_t minnfr;
oss_get_min_latency(context, *output_stream_params, &minnfr);
if (s->play.nfr < minnfr) {
s->play.nfr = minnfr;
s->play.nfrags = latency_frames / minnfr;
}
s->play.bufframes = s->play.nfrags * s->play.nfr;
} }
/* Use the largest nframes among playing and recording streams */
s->nfr = (playnfr > recnfr) ? playnfr : recnfr;
s->nfrags = OSS_NFRAGS;
s->bufframes = s->nfr * s->nfrags;
if (s->play.fd != -1) { if (s->play.fd != -1) {
int frag = oss_get_frag_params(oss_calc_frag_shift(s->nfr, s->play.frame_size)); int frag = oss_get_frag_params(
if (ioctl(s->record.fd, SNDCTL_DSP_SETFRAGMENT, &frag)) oss_calc_frag_shift(s->play.nfr, s->play.frame_size));
LOG("Failed to set record fd with SNDCTL_DSP_SETFRAGMENT. frag: 0x%x", if (ioctl(s->play.fd, SNDCTL_DSP_SETFRAGMENT, &frag))
LOG("Failed to set play fd with SNDCTL_DSP_SETFRAGMENT. frag: 0x%x",
frag); frag);
audio_buf_info bi;
if (ioctl(s->play.fd, SNDCTL_DSP_GETOSPACE, &bi))
LOG("Failed to get play fd's buffer info.");
else {
s->play.nfr = bi.fragsize / s->play.frame_size;
s->play.nfrags = bi.fragments;
s->play.bufframes = s->play.nfr * s->play.nfrags;
}
int lw = s->play.frame_size;
if (ioctl(s->play.fd, SNDCTL_DSP_LOW_WATER, &lw))
LOG("Audio device \"%s\" (play) could not set trigger threshold",
s->play.name);
} }
if (s->record.fd != -1) { if (s->record.fd != -1) {
int frag = oss_get_frag_params(oss_calc_frag_shift(s->nfr, s->record.frame_size)); int frag = oss_get_frag_params(
oss_calc_frag_shift(s->record.nfr, s->record.frame_size));
if (ioctl(s->record.fd, SNDCTL_DSP_SETFRAGMENT, &frag)) if (ioctl(s->record.fd, SNDCTL_DSP_SETFRAGMENT, &frag))
LOG("Failed to set record fd with SNDCTL_DSP_SETFRAGMENT. frag: 0x%x", LOG("Failed to set record fd with SNDCTL_DSP_SETFRAGMENT. frag: 0x%x",
frag); frag);
audio_buf_info bi;
if (ioctl(s->record.fd, SNDCTL_DSP_GETISPACE, &bi))
LOG("Failed to get record fd's buffer info.");
else {
s->record.nfr = bi.fragsize / s->record.frame_size;
s->record.nfrags = bi.fragments;
s->record.bufframes = s->record.nfr * s->record.nfrags;
}
int lw = s->record.frame_size;
if (ioctl(s->record.fd, SNDCTL_DSP_LOW_WATER, &lw))
LOG("Audio device \"%s\" (record) could not set trigger threshold",
s->record.name);
} }
s->context = context; s->context = context;
s->volume = 1.0; s->volume = 1.0;
@ -1119,13 +1199,14 @@ oss_stream_init(cubeb * context,
s->doorbell = false; s->doorbell = false;
if (s->play.fd != -1) { if (s->play.fd != -1) {
if ((s->play.buf = calloc(s->bufframes, s->play.frame_size)) == NULL) { if ((s->play.buf = calloc(s->play.bufframes, s->play.frame_size)) == NULL) {
ret = CUBEB_ERROR; ret = CUBEB_ERROR;
goto error; goto error;
} }
} }
if (s->record.fd != -1) { if (s->record.fd != -1) {
if ((s->record.buf = calloc(s->bufframes, s->record.frame_size)) == NULL) { if ((s->record.buf = calloc(s->record.bufframes, s->record.frame_size)) ==
NULL) {
ret = CUBEB_ERROR; ret = CUBEB_ERROR;
goto error; goto error;
} }
@ -1219,10 +1300,10 @@ oss_get_current_device(cubeb_stream * stream, cubeb_device ** const device)
if (*device == NULL) { if (*device == NULL) {
return CUBEB_ERROR; return CUBEB_ERROR;
} }
(*device)->input_name = stream->record.fd != -1 ? (*device)->input_name =
strdup(stream->record.name) : NULL; stream->record.fd != -1 ? strdup(stream->record.name) : NULL;
(*device)->output_name = stream->play.fd != -1 ? (*device)->output_name =
strdup(stream->play.name) : NULL; stream->play.fd != -1 ? strdup(stream->play.name) : NULL;
return CUBEB_OK; return CUBEB_OK;
} }
@ -1249,7 +1330,6 @@ static struct cubeb_ops const oss_ops = {
.stream_destroy = oss_stream_destroy, .stream_destroy = oss_stream_destroy,
.stream_start = oss_stream_start, .stream_start = oss_stream_start,
.stream_stop = oss_stream_stop, .stream_stop = oss_stream_stop,
.stream_reset_default_device = NULL,
.stream_get_position = oss_stream_get_position, .stream_get_position = oss_stream_get_position,
.stream_get_latency = oss_stream_get_latency, .stream_get_latency = oss_stream_get_latency,
.stream_get_input_latency = NULL, .stream_get_input_latency = NULL,

View file

@ -5,31 +5,29 @@
* accompanying file LICENSE for details. * accompanying file LICENSE for details.
*/ */
#include <cubeb/cubeb.h>
#include "cubeb_osx_run_loop.h" #include "cubeb_osx_run_loop.h"
#include "cubeb_log.h" #include "cubeb_log.h"
#include <AudioUnit/AudioUnit.h> #include <AudioUnit/AudioUnit.h>
#include <CoreAudio/AudioHardware.h> #include <CoreAudio/AudioHardware.h>
#include <CoreAudio/HostTime.h> #include <CoreAudio/HostTime.h>
#include <CoreFoundation/CoreFoundation.h> #include <CoreFoundation/CoreFoundation.h>
#include <cubeb/cubeb.h>
void cubeb_set_coreaudio_notification_runloop() void
cubeb_set_coreaudio_notification_runloop()
{ {
/* This is needed so that AudioUnit listeners get called on this thread, and /* This is needed so that AudioUnit listeners get called on this thread, and
* not the main thread. If we don't do that, they are not called, or a crash * not the main thread. If we don't do that, they are not called, or a crash
* occur, depending on the OSX version. */ * occur, depending on the OSX version. */
AudioObjectPropertyAddress runloop_address = { AudioObjectPropertyAddress runloop_address = {
kAudioHardwarePropertyRunLoop, kAudioHardwarePropertyRunLoop, kAudioObjectPropertyScopeGlobal,
kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster};
kAudioObjectPropertyElementMaster
};
CFRunLoopRef run_loop = nullptr; CFRunLoopRef run_loop = nullptr;
OSStatus r; OSStatus r;
r = AudioObjectSetPropertyData(kAudioObjectSystemObject, r = AudioObjectSetPropertyData(kAudioObjectSystemObject, &runloop_address, 0,
&runloop_address, NULL, sizeof(CFRunLoopRef), &run_loop);
0, NULL, sizeof(CFRunLoopRef), &run_loop);
if (r != noErr) { if (r != noErr) {
LOG("Could not make global CoreAudio notifications use their own thread."); LOG("Could not make global CoreAudio notifications use their own thread.");
} }

View file

@ -15,7 +15,8 @@
extern "C" { extern "C" {
#endif #endif
void cubeb_set_coreaudio_notification_runloop(); void
cubeb_set_coreaudio_notification_runloop();
#if defined(__cplusplus) #if defined(__cplusplus)
} }

File diff suppressed because it is too large Load diff

View file

@ -8,21 +8,21 @@
#define NOMINMAX #define NOMINMAX
#endif // NOMINMAX #endif // NOMINMAX
#include <algorithm>
#include <cmath>
#include <cassert>
#include <cstring>
#include <cstddef>
#include <cstdio>
#include "cubeb_resampler.h" #include "cubeb_resampler.h"
#include "cubeb-speex-resampler.h" #include "cubeb-speex-resampler.h"
#include "cubeb_resampler_internal.h" #include "cubeb_resampler_internal.h"
#include "cubeb_utils.h" #include "cubeb_utils.h"
#include <algorithm>
#include <cassert>
#include <cmath>
#include <cstddef>
#include <cstdio>
#include <cstring>
int int
to_speex_quality(cubeb_resampler_quality q) to_speex_quality(cubeb_resampler_quality q)
{ {
switch(q) { switch (q) {
case CUBEB_RESAMPLER_QUALITY_VOIP: case CUBEB_RESAMPLER_QUALITY_VOIP:
return SPEEX_RESAMPLER_QUALITY_VOIP; return SPEEX_RESAMPLER_QUALITY_VOIP;
case CUBEB_RESAMPLER_QUALITY_DEFAULT: case CUBEB_RESAMPLER_QUALITY_DEFAULT:
@ -35,34 +35,34 @@ to_speex_quality(cubeb_resampler_quality q)
} }
} }
uint32_t min_buffered_audio_frame(uint32_t sample_rate) uint32_t
min_buffered_audio_frame(uint32_t sample_rate)
{ {
return sample_rate / 20; return sample_rate / 20;
} }
template<typename T> template <typename T>
passthrough_resampler<T>::passthrough_resampler(cubeb_stream * s, passthrough_resampler<T>::passthrough_resampler(cubeb_stream * s,
cubeb_data_callback cb, cubeb_data_callback cb,
void * ptr, void * ptr,
uint32_t input_channels, uint32_t input_channels,
uint32_t sample_rate) uint32_t sample_rate)
: processor(input_channels) : processor(input_channels), stream(s), data_callback(cb), user_ptr(ptr),
, stream(s) sample_rate(sample_rate)
, data_callback(cb)
, user_ptr(ptr)
, sample_rate(sample_rate)
{ {
} }
template<typename T> template <typename T>
long passthrough_resampler<T>::fill(void * input_buffer, long * input_frames_count, long
void * output_buffer, long output_frames) passthrough_resampler<T>::fill(void * input_buffer, long * input_frames_count,
void * output_buffer, long output_frames)
{ {
if (input_buffer) { if (input_buffer) {
assert(input_frames_count); assert(input_frames_count);
} }
assert((input_buffer && output_buffer) || assert((input_buffer && output_buffer) ||
(output_buffer && !input_buffer && (!input_frames_count || *input_frames_count == 0)) || (output_buffer && !input_buffer &&
(!input_frames_count || *input_frames_count == 0)) ||
(input_buffer && !output_buffer && output_frames == 0)); (input_buffer && !output_buffer && output_frames == 0));
// When we have no pending input data and exactly as much input // When we have no pending input data and exactly as much input
@ -71,41 +71,44 @@ long passthrough_resampler<T>::fill(void * input_buffer, long * input_frames_cou
void * in_buf = input_buffer; void * in_buf = input_buffer;
unsigned long pop_input_count = 0u; unsigned long pop_input_count = 0u;
if (input_buffer && !output_buffer) { if (input_buffer && !output_buffer) {
output_frames = *input_frames_count; output_frames = *input_frames_count;
} else if(input_buffer) { } else if (input_buffer) {
if (internal_input_buffer.length() != 0 || if (internal_input_buffer.length() != 0 ||
*input_frames_count < output_frames) { *input_frames_count < output_frames) {
// If we have pending input data left and have to first append the input // If we have pending input data left and have to first append the input
// so we can pass it as one pointer to the callback. Or this is a glitch. // so we can pass it as one pointer to the callback. Or this is a glitch.
// It can happen when system's performance is poor. Audible silence is // It can happen when system's performance is poor. Audible silence is
// being pushed at the end of the short input buffer. An improvement for // being pushed at the end of the short input buffer. An improvement for
// the future is to resample to the output number of frames, when that happens. // the future is to resample to the output number of frames, when that
internal_input_buffer.push(static_cast<T*>(input_buffer), // happens.
internal_input_buffer.push(static_cast<T *>(input_buffer),
frames_to_samples(*input_frames_count)); frames_to_samples(*input_frames_count));
if (internal_input_buffer.length() < frames_to_samples(output_frames)) { if (internal_input_buffer.length() < frames_to_samples(output_frames)) {
// This is unxpected but it can happen when a glitch occurs. Fill the // This is unxpected but it can happen when a glitch occurs. Fill the
// buffer with silence. First keep the actual number of input samples // buffer with silence. First keep the actual number of input samples
// used without the silence. // used without the silence.
pop_input_count = internal_input_buffer.length(); pop_input_count = internal_input_buffer.length();
internal_input_buffer.push_silence( internal_input_buffer.push_silence(frames_to_samples(output_frames) -
frames_to_samples(output_frames) - internal_input_buffer.length()); internal_input_buffer.length());
} else { } else {
pop_input_count = frames_to_samples(output_frames); pop_input_count = frames_to_samples(output_frames);
} }
in_buf = internal_input_buffer.data(); in_buf = internal_input_buffer.data();
} else if(*input_frames_count > output_frames) { } else if (*input_frames_count > output_frames) {
// In this case we have more input that we need output and // In this case we have more input that we need output and
// fill the overflowing input into internal_input_buffer // fill the overflowing input into internal_input_buffer
// Since we have no other pending data, we can nonetheless // Since we have no other pending data, we can nonetheless
// pass the current input data directly to the callback // pass the current input data directly to the callback
assert(pop_input_count == 0); assert(pop_input_count == 0);
unsigned long samples_off = frames_to_samples(output_frames); unsigned long samples_off = frames_to_samples(output_frames);
internal_input_buffer.push(static_cast<T*>(input_buffer) + samples_off, internal_input_buffer.push(
frames_to_samples(*input_frames_count - output_frames)); static_cast<T *>(input_buffer) + samples_off,
frames_to_samples(*input_frames_count - output_frames));
} }
} }
long rv = data_callback(stream, user_ptr, in_buf, output_buffer, output_frames); long rv =
data_callback(stream, user_ptr, in_buf, output_buffer, output_frames);
if (input_buffer) { if (input_buffer) {
if (pop_input_count) { if (pop_input_count) {
@ -124,51 +127,47 @@ long passthrough_resampler<T>::fill(void * input_buffer, long * input_frames_cou
template class passthrough_resampler<float>; template class passthrough_resampler<float>;
template class passthrough_resampler<short>; template class passthrough_resampler<short>;
template<typename T, typename InputProcessor, typename OutputProcessor> template <typename T, typename InputProcessor, typename OutputProcessor>
cubeb_resampler_speex<T, InputProcessor, OutputProcessor> cubeb_resampler_speex<T, InputProcessor, OutputProcessor>::
::cubeb_resampler_speex(InputProcessor * input_processor, cubeb_resampler_speex(InputProcessor * input_processor,
OutputProcessor * output_processor, OutputProcessor * output_processor, cubeb_stream * s,
cubeb_stream * s, cubeb_data_callback cb, void * ptr)
cubeb_data_callback cb, : input_processor(input_processor), output_processor(output_processor),
void * ptr) stream(s), data_callback(cb), user_ptr(ptr)
: input_processor(input_processor)
, output_processor(output_processor)
, stream(s)
, data_callback(cb)
, user_ptr(ptr)
{ {
if (input_processor && output_processor) { if (input_processor && output_processor) {
fill_internal = &cubeb_resampler_speex::fill_internal_duplex; fill_internal = &cubeb_resampler_speex::fill_internal_duplex;
} else if (input_processor) { } else if (input_processor) {
fill_internal = &cubeb_resampler_speex::fill_internal_input; fill_internal = &cubeb_resampler_speex::fill_internal_input;
} else if (output_processor) { } else if (output_processor) {
fill_internal = &cubeb_resampler_speex::fill_internal_output; fill_internal = &cubeb_resampler_speex::fill_internal_output;
} }
} }
template<typename T, typename InputProcessor, typename OutputProcessor> template <typename T, typename InputProcessor, typename OutputProcessor>
cubeb_resampler_speex<T, InputProcessor, OutputProcessor> cubeb_resampler_speex<T, InputProcessor,
::~cubeb_resampler_speex() OutputProcessor>::~cubeb_resampler_speex()
{ }
template<typename T, typename InputProcessor, typename OutputProcessor>
long
cubeb_resampler_speex<T, InputProcessor, OutputProcessor>
::fill(void * input_buffer, long * input_frames_count,
void * output_buffer, long output_frames_needed)
{ {
/* Input and output buffers, typed */
T * in_buffer = reinterpret_cast<T*>(input_buffer);
T * out_buffer = reinterpret_cast<T*>(output_buffer);
return (this->*fill_internal)(in_buffer, input_frames_count,
out_buffer, output_frames_needed);
} }
template<typename T, typename InputProcessor, typename OutputProcessor> template <typename T, typename InputProcessor, typename OutputProcessor>
long long
cubeb_resampler_speex<T, InputProcessor, OutputProcessor> cubeb_resampler_speex<T, InputProcessor, OutputProcessor>::fill(
::fill_internal_output(T * input_buffer, long * input_frames_count, void * input_buffer, long * input_frames_count, void * output_buffer,
T * output_buffer, long output_frames_needed) long output_frames_needed)
{
/* Input and output buffers, typed */
T * in_buffer = reinterpret_cast<T *>(input_buffer);
T * out_buffer = reinterpret_cast<T *>(output_buffer);
return (this->*fill_internal)(in_buffer, input_frames_count, out_buffer,
output_frames_needed);
}
template <typename T, typename InputProcessor, typename OutputProcessor>
long
cubeb_resampler_speex<T, InputProcessor, OutputProcessor>::fill_internal_output(
T * input_buffer, long * input_frames_count, T * output_buffer,
long output_frames_needed)
{ {
assert(!input_buffer && (!input_frames_count || *input_frames_count == 0) && assert(!input_buffer && (!input_frames_count || *input_frames_count == 0) &&
output_buffer && output_frames_needed); output_buffer && output_frames_needed);
@ -180,13 +179,12 @@ cubeb_resampler_speex<T, InputProcessor, OutputProcessor>
/* fill directly the input buffer of the output processor to save a copy */ /* fill directly the input buffer of the output processor to save a copy */
output_frames_before_processing = output_frames_before_processing =
output_processor->input_needed_for_output(output_frames_needed); output_processor->input_needed_for_output(output_frames_needed);
out_unprocessed = out_unprocessed =
output_processor->input_buffer(output_frames_before_processing); output_processor->input_buffer(output_frames_before_processing);
got = data_callback(stream, user_ptr, got = data_callback(stream, user_ptr, nullptr, out_unprocessed,
nullptr, out_unprocessed,
output_frames_before_processing); output_frames_before_processing);
if (got < output_frames_before_processing) { if (got < output_frames_before_processing) {
@ -201,52 +199,62 @@ cubeb_resampler_speex<T, InputProcessor, OutputProcessor>
} }
/* Process the output. If not enough frames have been returned from the /* Process the output. If not enough frames have been returned from the
* callback, drain the processors. */ * callback, drain the processors. */
return output_processor->output(output_buffer, output_frames_needed); return output_processor->output(output_buffer, output_frames_needed);
} }
template<typename T, typename InputProcessor, typename OutputProcessor> template <typename T, typename InputProcessor, typename OutputProcessor>
long long
cubeb_resampler_speex<T, InputProcessor, OutputProcessor> cubeb_resampler_speex<T, InputProcessor, OutputProcessor>::fill_internal_input(
::fill_internal_input(T * input_buffer, long * input_frames_count, T * input_buffer, long * input_frames_count, T * output_buffer,
T * output_buffer, long /*output_frames_needed*/) long /*output_frames_needed*/)
{ {
assert(input_buffer && input_frames_count && *input_frames_count && assert(input_buffer && input_frames_count && *input_frames_count &&
!output_buffer); !output_buffer);
/* The input data, after eventual resampling. This is passed to the callback. */ /* The input data, after eventual resampling. This is passed to the callback.
*/
T * resampled_input = nullptr; T * resampled_input = nullptr;
uint32_t resampled_frame_count = input_processor->output_for_input(*input_frames_count); uint32_t resampled_frame_count =
input_processor->output_for_input(*input_frames_count);
/* process the input, and present exactly `output_frames_needed` in the /* process the input, and present exactly `output_frames_needed` in the
* callback. */ * callback. */
input_processor->input(input_buffer, *input_frames_count); input_processor->input(input_buffer, *input_frames_count);
/* resampled_frame_count == 0 happens if the resampler
* doesn't have enough input frames buffered to produce 1 resampled frame. */
if (resampled_frame_count == 0) {
return *input_frames_count;
}
size_t frames_resampled = 0; size_t frames_resampled = 0;
resampled_input = input_processor->output(resampled_frame_count, &frames_resampled); resampled_input =
input_processor->output(resampled_frame_count, &frames_resampled);
*input_frames_count = frames_resampled; *input_frames_count = frames_resampled;
long got = data_callback(stream, user_ptr, long got = data_callback(stream, user_ptr, resampled_input, nullptr,
resampled_input, nullptr, resampled_frame_count); resampled_frame_count);
/* Return the number of initial input frames or part of it. /* Return the number of initial input frames or part of it.
* Since output_frames_needed == 0 in input scenario, the only * Since output_frames_needed == 0 in input scenario, the only
* available number outside resampler is the initial number of frames. */ * available number outside resampler is the initial number of frames. */
return (*input_frames_count) * (got / resampled_frame_count); return (*input_frames_count) * (got / resampled_frame_count);
} }
template<typename T, typename InputProcessor, typename OutputProcessor> template <typename T, typename InputProcessor, typename OutputProcessor>
long long
cubeb_resampler_speex<T, InputProcessor, OutputProcessor> cubeb_resampler_speex<T, InputProcessor, OutputProcessor>::fill_internal_duplex(
::fill_internal_duplex(T * in_buffer, long * input_frames_count, T * in_buffer, long * input_frames_count, T * out_buffer,
T * out_buffer, long output_frames_needed) long output_frames_needed)
{ {
if (draining) { if (draining) {
// discard input and drain any signal remaining in the resampler. // discard input and drain any signal remaining in the resampler.
return output_processor->output(out_buffer, output_frames_needed); return output_processor->output(out_buffer, output_frames_needed);
} }
/* The input data, after eventual resampling. This is passed to the callback. */ /* The input data, after eventual resampling. This is passed to the callback.
*/
T * resampled_input = nullptr; T * resampled_input = nullptr;
/* The output buffer passed down in the callback, that might be resampled. */ /* The output buffer passed down in the callback, that might be resampled. */
T * out_unprocessed = nullptr; T * out_unprocessed = nullptr;
@ -266,26 +274,25 @@ cubeb_resampler_speex<T, InputProcessor, OutputProcessor>
* caller. */ * caller. */
output_frames_before_processing = output_frames_before_processing =
output_processor->input_needed_for_output(output_frames_needed); output_processor->input_needed_for_output(output_frames_needed);
/* fill directly the input buffer of the output processor to save a copy */ /* fill directly the input buffer of the output processor to save a copy */
out_unprocessed = out_unprocessed =
output_processor->input_buffer(output_frames_before_processing); output_processor->input_buffer(output_frames_before_processing);
if (in_buffer) { if (in_buffer) {
/* process the input, and present exactly `output_frames_needed` in the /* process the input, and present exactly `output_frames_needed` in the
* callback. */ * callback. */
input_processor->input(in_buffer, *input_frames_count); input_processor->input(in_buffer, *input_frames_count);
size_t frames_resampled = 0; size_t frames_resampled = 0;
resampled_input = resampled_input = input_processor->output(output_frames_before_processing,
input_processor->output(output_frames_before_processing, &frames_resampled); &frames_resampled);
*input_frames_count = frames_resampled; *input_frames_count = frames_resampled;
} else { } else {
resampled_input = nullptr; resampled_input = nullptr;
} }
got = data_callback(stream, user_ptr, got = data_callback(stream, user_ptr, resampled_input, out_unprocessed,
resampled_input, out_unprocessed,
output_frames_before_processing); output_frames_before_processing);
if (got < output_frames_before_processing) { if (got < output_frames_before_processing) {
@ -315,10 +322,8 @@ cubeb_resampler *
cubeb_resampler_create(cubeb_stream * stream, cubeb_resampler_create(cubeb_stream * stream,
cubeb_stream_params * input_params, cubeb_stream_params * input_params,
cubeb_stream_params * output_params, cubeb_stream_params * output_params,
unsigned int target_rate, unsigned int target_rate, cubeb_data_callback callback,
cubeb_data_callback callback, void * user_ptr, cubeb_resampler_quality quality)
void * user_ptr,
cubeb_resampler_quality quality)
{ {
cubeb_sample_format format; cubeb_sample_format format;
@ -330,38 +335,28 @@ cubeb_resampler_create(cubeb_stream * stream,
format = output_params->format; format = output_params->format;
} }
switch(format) { switch (format) {
case CUBEB_SAMPLE_S16NE: case CUBEB_SAMPLE_S16NE:
return cubeb_resampler_create_internal<short>(stream, return cubeb_resampler_create_internal<short>(stream, input_params,
input_params, output_params, target_rate,
output_params, callback, user_ptr, quality);
target_rate, case CUBEB_SAMPLE_FLOAT32NE:
callback, return cubeb_resampler_create_internal<float>(stream, input_params,
user_ptr, output_params, target_rate,
quality); callback, user_ptr, quality);
case CUBEB_SAMPLE_FLOAT32NE: default:
return cubeb_resampler_create_internal<float>(stream, assert(false);
input_params, return nullptr;
output_params,
target_rate,
callback,
user_ptr,
quality);
default:
assert(false);
return nullptr;
} }
} }
long long
cubeb_resampler_fill(cubeb_resampler * resampler, cubeb_resampler_fill(cubeb_resampler * resampler, void * input_buffer,
void * input_buffer, long * input_frames_count, void * output_buffer,
long * input_frames_count,
void * output_buffer,
long output_frames_needed) long output_frames_needed)
{ {
return resampler->fill(input_buffer, input_frames_count, return resampler->fill(input_buffer, input_frames_count, output_buffer,
output_buffer, output_frames_needed); output_frames_needed);
} }
void void

View file

@ -39,13 +39,12 @@ typedef enum {
* @param quality Quality of the resampler. * @param quality Quality of the resampler.
* @retval A non-null pointer if success. * @retval A non-null pointer if success.
*/ */
cubeb_resampler * cubeb_resampler_create(cubeb_stream * stream, cubeb_resampler *
cubeb_stream_params * input_params, cubeb_resampler_create(cubeb_stream * stream,
cubeb_stream_params * output_params, cubeb_stream_params * input_params,
unsigned int target_rate, cubeb_stream_params * output_params,
cubeb_data_callback callback, unsigned int target_rate, cubeb_data_callback callback,
void * user_ptr, void * user_ptr, cubeb_resampler_quality quality);
cubeb_resampler_quality quality);
/** /**
* Fill the buffer with frames acquired using the data callback. Resampling will * Fill the buffer with frames acquired using the data callback. Resampling will
@ -59,24 +58,25 @@ cubeb_resampler * cubeb_resampler_create(cubeb_stream * stream,
* @retval Number of frames that are actually produced. * @retval Number of frames that are actually produced.
* @retval CUBEB_ERROR on error. * @retval CUBEB_ERROR on error.
*/ */
long cubeb_resampler_fill(cubeb_resampler * resampler, long
void * input_buffer, cubeb_resampler_fill(cubeb_resampler * resampler, void * input_buffer,
long * input_frame_count, long * input_frame_count, void * output_buffer,
void * output_buffer, long output_frames_needed);
long output_frames_needed);
/** /**
* Destroy a cubeb_resampler. * Destroy a cubeb_resampler.
* @param resampler A cubeb_resampler instance. * @param resampler A cubeb_resampler instance.
*/ */
void cubeb_resampler_destroy(cubeb_resampler * resampler); void
cubeb_resampler_destroy(cubeb_resampler * resampler);
/** /**
* Returns the latency, in frames, of the resampler. * Returns the latency, in frames, of the resampler.
* @param resampler A cubeb resampler instance. * @param resampler A cubeb resampler instance.
* @retval The latency, in frames, induced by the resampler. * @retval The latency, in frames, induced by the resampler.
*/ */
long cubeb_resampler_latency(cubeb_resampler * resampler); long
cubeb_resampler_latency(cubeb_resampler * resampler);
#if defined(__cplusplus) #if defined(__cplusplus)
} }

View file

@ -8,9 +8,9 @@
#if !defined(CUBEB_RESAMPLER_INTERNAL) #if !defined(CUBEB_RESAMPLER_INTERNAL)
#define CUBEB_RESAMPLER_INTERNAL #define CUBEB_RESAMPLER_INTERNAL
#include <cmath>
#include <cassert>
#include <algorithm> #include <algorithm>
#include <cassert>
#include <cmath>
#include <memory> #include <memory>
#ifdef CUBEB_GECKO_BUILD #ifdef CUBEB_GECKO_BUILD
#include "mozilla/UniquePtr.h" #include "mozilla/UniquePtr.h"
@ -25,29 +25,32 @@
#define MOZ_END_STD_NAMESPACE } #define MOZ_END_STD_NAMESPACE }
#endif #endif
MOZ_BEGIN_STD_NAMESPACE MOZ_BEGIN_STD_NAMESPACE
using mozilla::DefaultDelete; using mozilla::DefaultDelete;
using mozilla::UniquePtr; using mozilla::UniquePtr;
#define default_delete DefaultDelete #define default_delete DefaultDelete
#define unique_ptr UniquePtr #define unique_ptr UniquePtr
MOZ_END_STD_NAMESPACE MOZ_END_STD_NAMESPACE
#endif #endif
#include "cubeb/cubeb.h"
#include "cubeb_utils.h"
#include "cubeb-speex-resampler.h" #include "cubeb-speex-resampler.h"
#include "cubeb_resampler.h" #include "cubeb/cubeb.h"
#include "cubeb_log.h" #include "cubeb_log.h"
#include "cubeb_resampler.h"
#include "cubeb_utils.h"
#include <stdio.h> #include <stdio.h>
/* This header file contains the internal C++ API of the resamplers, for testing. */ /* This header file contains the internal C++ API of the resamplers, for
* testing. */
// When dropping audio input frames to prevent building // When dropping audio input frames to prevent building
// an input delay, this function returns the number of frames // an input delay, this function returns the number of frames
// to keep in the buffer. // to keep in the buffer.
// @parameter sample_rate The sample rate of the stream. // @parameter sample_rate The sample rate of the stream.
// @return A number of frames to keep. // @return A number of frames to keep.
uint32_t min_buffered_audio_frame(uint32_t sample_rate); uint32_t
min_buffered_audio_frame(uint32_t sample_rate);
int to_speex_quality(cubeb_resampler_quality q); int
to_speex_quality(cubeb_resampler_quality q);
struct cubeb_resampler { struct cubeb_resampler {
virtual long fill(void * input_buffer, long * input_frames_count, virtual long fill(void * input_buffer, long * input_frames_count,
@ -59,14 +62,10 @@ struct cubeb_resampler {
/** Base class for processors. This is just used to share methods for now. */ /** Base class for processors. This is just used to share methods for now. */
class processor { class processor {
public: public:
explicit processor(uint32_t channels) explicit processor(uint32_t channels) : channels(channels) {}
: channels(channels)
{}
protected: protected:
size_t frames_to_samples(size_t frames) const size_t frames_to_samples(size_t frames) const { return frames * channels; }
{
return frames * channels;
}
size_t samples_to_frames(size_t samples) const size_t samples_to_frames(size_t samples) const
{ {
assert(!(samples % channels)); assert(!(samples % channels));
@ -76,30 +75,24 @@ protected:
const uint32_t channels; const uint32_t channels;
}; };
template<typename T> template <typename T>
class passthrough_resampler : public cubeb_resampler class passthrough_resampler : public cubeb_resampler, public processor {
, public processor {
public: public:
passthrough_resampler(cubeb_stream * s, passthrough_resampler(cubeb_stream * s, cubeb_data_callback cb, void * ptr,
cubeb_data_callback cb, uint32_t input_channels, uint32_t sample_rate);
void * ptr,
uint32_t input_channels,
uint32_t sample_rate);
virtual long fill(void * input_buffer, long * input_frames_count, virtual long fill(void * input_buffer, long * input_frames_count,
void * output_buffer, long output_frames); void * output_buffer, long output_frames);
virtual long latency() virtual long latency() { return 0; }
{
return 0;
}
void drop_audio_if_needed() void drop_audio_if_needed()
{ {
uint32_t to_keep = min_buffered_audio_frame(sample_rate); uint32_t to_keep = min_buffered_audio_frame(sample_rate);
uint32_t available = samples_to_frames(internal_input_buffer.length()); uint32_t available = samples_to_frames(internal_input_buffer.length());
if (available > to_keep) { if (available > to_keep) {
internal_input_buffer.pop(nullptr, frames_to_samples(available - to_keep)); internal_input_buffer.pop(nullptr,
frames_to_samples(available - to_keep));
} }
} }
@ -116,14 +109,12 @@ private:
/** Bidirectional resampler, can resample an input and an output stream, or just /** Bidirectional resampler, can resample an input and an output stream, or just
* an input stream or output stream. In this case a delay is inserted in the * an input stream or output stream. In this case a delay is inserted in the
* opposite direction to keep the streams synchronized. */ * opposite direction to keep the streams synchronized. */
template<typename T, typename InputProcessing, typename OutputProcessing> template <typename T, typename InputProcessing, typename OutputProcessing>
class cubeb_resampler_speex : public cubeb_resampler { class cubeb_resampler_speex : public cubeb_resampler {
public: public:
cubeb_resampler_speex(InputProcessing * input_processor, cubeb_resampler_speex(InputProcessing * input_processor,
OutputProcessing * output_processor, OutputProcessing * output_processor, cubeb_stream * s,
cubeb_stream * s, cubeb_data_callback cb, void * ptr);
cubeb_data_callback cb,
void * ptr);
virtual ~cubeb_resampler_speex(); virtual ~cubeb_resampler_speex();
@ -143,7 +134,9 @@ public:
} }
private: private:
typedef long(cubeb_resampler_speex::*processing_callback)(T * input_buffer, long * input_frames_count, T * output_buffer, long output_frames_needed); typedef long (cubeb_resampler_speex::*processing_callback)(
T * input_buffer, long * input_frames_count, T * output_buffer,
long output_frames_needed);
long fill_internal_duplex(T * input_buffer, long * input_frames_count, long fill_internal_duplex(T * input_buffer, long * input_frames_count,
T * output_buffer, long output_frames_needed); T * output_buffer, long output_frames_needed);
@ -165,8 +158,7 @@ private:
* audio buffers of type T. This class is designed so that the number of frames * audio buffers of type T. This class is designed so that the number of frames
* coming out of the resampler can be precisely controled. It manages its own * coming out of the resampler can be precisely controled. It manages its own
* input buffer, and can use the caller's output buffer, or allocate its own. */ * input buffer, and can use the caller's output buffer, or allocate its own. */
template<typename T> template <typename T> class cubeb_resampler_speex_one_way : public processor {
class cubeb_resampler_speex_one_way : public processor {
public: public:
/** The sample type of this resampler, either 16-bit integers or 32-bit /** The sample type of this resampler, either 16-bit integers or 32-bit
* floats. */ * floats. */
@ -178,19 +170,15 @@ public:
* @parameter target_rate The sample-rate of the audio output. * @parameter target_rate The sample-rate of the audio output.
* @parameter quality A number between 0 (fast, low quality) and 10 (slow, * @parameter quality A number between 0 (fast, low quality) and 10 (slow,
* high quality). */ * high quality). */
cubeb_resampler_speex_one_way(uint32_t channels, cubeb_resampler_speex_one_way(uint32_t channels, uint32_t source_rate,
uint32_t source_rate, uint32_t target_rate, int quality)
uint32_t target_rate, : processor(channels),
int quality) resampling_ratio(static_cast<float>(source_rate) / target_rate),
: processor(channels) source_rate(source_rate), additional_latency(0), leftover_samples(0)
, resampling_ratio(static_cast<float>(source_rate) / target_rate)
, source_rate(source_rate)
, additional_latency(0)
, leftover_samples(0)
{ {
int r; int r;
speex_resampler = speex_resampler_init(channels, source_rate, speex_resampler =
target_rate, quality, &r); speex_resampler_init(channels, source_rate, target_rate, quality, &r);
assert(r == RESAMPLER_ERR_SUCCESS && "resampler allocation failure"); assert(r == RESAMPLER_ERR_SUCCESS && "resampler allocation failure");
uint32_t input_latency = speex_resampler_get_input_latency(speex_resampler); uint32_t input_latency = speex_resampler_get_input_latency(speex_resampler);
@ -200,11 +188,8 @@ public:
uint32_t input_frame_count = input_latency; uint32_t input_frame_count = input_latency;
uint32_t output_frame_count = LATENCY_SAMPLES; uint32_t output_frame_count = LATENCY_SAMPLES;
assert(input_latency * channels <= LATENCY_SAMPLES); assert(input_latency * channels <= LATENCY_SAMPLES);
speex_resample( speex_resample(input_buffer, &input_frame_count, output_buffer,
input_buffer, &output_frame_count);
&input_frame_count,
output_buffer,
&output_frame_count);
} }
/** Destructor, deallocate the resampler */ /** Destructor, deallocate the resampler */
@ -221,14 +206,14 @@ public:
} }
/** Outputs exactly `output_frame_count` into `output_buffer`. /** Outputs exactly `output_frame_count` into `output_buffer`.
* `output_buffer` has to be at least `output_frame_count` long. */ * `output_buffer` has to be at least `output_frame_count` long. */
size_t output(T * output_buffer, size_t output_frame_count) size_t output(T * output_buffer, size_t output_frame_count)
{ {
uint32_t in_len = samples_to_frames(resampling_in_buffer.length()); uint32_t in_len = samples_to_frames(resampling_in_buffer.length());
uint32_t out_len = output_frame_count; uint32_t out_len = output_frame_count;
speex_resample(resampling_in_buffer.data(), &in_len, speex_resample(resampling_in_buffer.data(), &in_len, output_buffer,
output_buffer, &out_len); &out_len);
/* This shifts back any unresampled samples to the beginning of the input /* This shifts back any unresampled samples to the beginning of the input
buffer. */ buffer. */
@ -239,15 +224,17 @@ public:
size_t output_for_input(uint32_t input_frames) size_t output_for_input(uint32_t input_frames)
{ {
return (size_t)floorf((input_frames + samples_to_frames(resampling_in_buffer.length())) return (size_t)floorf(
/ resampling_ratio); (input_frames + samples_to_frames(resampling_in_buffer.length())) /
resampling_ratio);
} }
/** Returns a buffer containing exactly `output_frame_count` resampled frames. /** Returns a buffer containing exactly `output_frame_count` resampled frames.
* The consumer should not hold onto the pointer. */ * The consumer should not hold onto the pointer. */
T * output(size_t output_frame_count, size_t * input_frames_used) T * output(size_t output_frame_count, size_t * input_frames_used)
{ {
if (resampling_out_buffer.capacity() < frames_to_samples(output_frame_count)) { if (resampling_out_buffer.capacity() <
frames_to_samples(output_frame_count)) {
resampling_out_buffer.reserve(frames_to_samples(output_frame_count)); resampling_out_buffer.reserve(frames_to_samples(output_frame_count));
} }
@ -258,10 +245,12 @@ public:
resampling_out_buffer.data(), &out_len); resampling_out_buffer.data(), &out_len);
if (out_len < output_frame_count) { if (out_len < output_frame_count) {
LOGV("underrun during resampling: got %u frames, expected %zu", (unsigned)out_len, output_frame_count); LOGV("underrun during resampling: got %u frames, expected %zu",
(unsigned)out_len, output_frame_count);
// silence the rightmost part // silence the rightmost part
T* data = resampling_out_buffer.data(); T * data = resampling_out_buffer.data();
for (uint32_t i = frames_to_samples(out_len); i < frames_to_samples(output_frame_count); i++) { for (uint32_t i = frames_to_samples(out_len);
i < frames_to_samples(output_frame_count); i++) {
data[i] = 0; data[i] = 0;
} }
} }
@ -281,8 +270,8 @@ public:
* only consider a single channel here so it's the same number of frames. */ * only consider a single channel here so it's the same number of frames. */
int latency = 0; int latency = 0;
latency = latency = speex_resampler_get_output_latency(speex_resampler) +
speex_resampler_get_output_latency(speex_resampler) + additional_latency; additional_latency;
assert(latency >= 0); assert(latency >= 0);
@ -296,11 +285,13 @@ public:
uint32_t input_needed_for_output(int32_t output_frame_count) const uint32_t input_needed_for_output(int32_t output_frame_count) const
{ {
assert(output_frame_count >= 0); // Check overflow assert(output_frame_count >= 0); // Check overflow
int32_t unresampled_frames_left = samples_to_frames(resampling_in_buffer.length()); int32_t unresampled_frames_left =
int32_t resampled_frames_left = samples_to_frames(resampling_out_buffer.length()); samples_to_frames(resampling_in_buffer.length());
int32_t resampled_frames_left =
samples_to_frames(resampling_out_buffer.length());
float input_frames_needed = float input_frames_needed =
(output_frame_count - unresampled_frames_left) * resampling_ratio (output_frame_count - unresampled_frames_left) * resampling_ratio -
- resampled_frames_left; resampled_frames_left;
if (input_frames_needed < 0) { if (input_frames_needed < 0) {
return 0; return 0;
} }
@ -337,9 +328,10 @@ public:
resampling_in_buffer.pop(nullptr, frames_to_samples(available - to_keep)); resampling_in_buffer.pop(nullptr, frames_to_samples(available - to_keep));
} }
} }
private: private:
/** Wrapper for the speex resampling functions to have a typed /** Wrapper for the speex resampling functions to have a typed
* interface. */ * interface. */
void speex_resample(float * input_buffer, uint32_t * input_frame_count, void speex_resample(float * input_buffer, uint32_t * input_frame_count,
float * output_buffer, uint32_t * output_frame_count) float * output_buffer, uint32_t * output_frame_count)
{ {
@ -347,11 +339,9 @@ private:
int rv; int rv;
rv = rv =
#endif #endif
speex_resampler_process_interleaved_float(speex_resampler, speex_resampler_process_interleaved_float(
input_buffer, speex_resampler, input_buffer, input_frame_count, output_buffer,
input_frame_count, output_frame_count);
output_buffer,
output_frame_count);
assert(rv == RESAMPLER_ERR_SUCCESS); assert(rv == RESAMPLER_ERR_SUCCESS);
} }
@ -362,11 +352,9 @@ private:
int rv; int rv;
rv = rv =
#endif #endif
speex_resampler_process_interleaved_int(speex_resampler, speex_resampler_process_interleaved_int(
input_buffer, speex_resampler, input_buffer, input_frame_count, output_buffer,
input_frame_count, output_frame_count);
output_buffer,
output_frame_count);
assert(rv == RESAMPLER_ERR_SUCCESS); assert(rv == RESAMPLER_ERR_SUCCESS);
} }
/** The state for the speex resampler used internaly. */ /** The state for the speex resampler used internaly. */
@ -387,18 +375,16 @@ private:
}; };
/** This class allows delaying an audio stream by `frames` frames. */ /** This class allows delaying an audio stream by `frames` frames. */
template<typename T> template <typename T> class delay_line : public processor {
class delay_line : public processor {
public: public:
/** Constructor /** Constructor
* @parameter frames the number of frames of delay. * @parameter frames the number of frames of delay.
* @parameter channels the number of channels of this delay line. * @parameter channels the number of channels of this delay line.
* @parameter sample_rate sample-rate of the audio going through this delay line */ * @parameter sample_rate sample-rate of the audio going through this delay
* line */
delay_line(uint32_t frames, uint32_t channels, uint32_t sample_rate) delay_line(uint32_t frames, uint32_t channels, uint32_t sample_rate)
: processor(channels) : processor(channels), length(frames), leftover_samples(0),
, length(frames) sample_rate(sample_rate)
, leftover_samples(0)
, sample_rate(sample_rate)
{ {
/* Fill the delay line with some silent frames to add latency. */ /* Fill the delay line with some silent frames to add latency. */
delay_input_buffer.push_silence(frames * channels); delay_input_buffer.push_silence(frames * channels);
@ -436,7 +422,8 @@ public:
T * input_buffer(uint32_t frames_needed) T * input_buffer(uint32_t frames_needed)
{ {
leftover_samples = delay_input_buffer.length(); leftover_samples = delay_input_buffer.length();
delay_input_buffer.reserve(leftover_samples + frames_to_samples(frames_needed)); delay_input_buffer.reserve(leftover_samples +
frames_to_samples(frames_needed));
return delay_input_buffer.data() + leftover_samples; return delay_input_buffer.data() + leftover_samples;
} }
/** This method works with `input_buffer`, and allows to inform the processor /** This method works with `input_buffer`, and allows to inform the processor
@ -471,17 +458,12 @@ public:
assert(frames_needed >= 0); // Check overflow assert(frames_needed >= 0); // Check overflow
return frames_needed; return frames_needed;
} }
/** Returns the number of frames produces for `input_frames` frames in input */ /** Returns the number of frames produces for `input_frames` frames in input
size_t output_for_input(uint32_t input_frames) */
{ size_t output_for_input(uint32_t input_frames) { return input_frames; }
return input_frames;
}
/** The number of frames this delay line delays the stream by. /** The number of frames this delay line delays the stream by.
* @returns The number of frames of delay. */ * @returns The number of frames of delay. */
size_t latency() size_t latency() { return length; }
{
return length;
}
void drop_audio_if_needed() void drop_audio_if_needed()
{ {
@ -491,6 +473,7 @@ public:
delay_input_buffer.pop(nullptr, frames_to_samples(available - to_keep)); delay_input_buffer.pop(nullptr, frames_to_samples(available - to_keep));
} }
} }
private: private:
/** The length, in frames, of this delay line */ /** The length, in frames, of this delay line */
uint32_t length; uint32_t length;
@ -506,14 +489,13 @@ private:
}; };
/** This sits behind the C API and is more typed. */ /** This sits behind the C API and is more typed. */
template<typename T> template <typename T>
cubeb_resampler * cubeb_resampler *
cubeb_resampler_create_internal(cubeb_stream * stream, cubeb_resampler_create_internal(cubeb_stream * stream,
cubeb_stream_params * input_params, cubeb_stream_params * input_params,
cubeb_stream_params * output_params, cubeb_stream_params * output_params,
unsigned int target_rate, unsigned int target_rate,
cubeb_data_callback callback, cubeb_data_callback callback, void * user_ptr,
void * user_ptr,
cubeb_resampler_quality quality) cubeb_resampler_quality quality)
{ {
std::unique_ptr<cubeb_resampler_speex_one_way<T>> input_resampler = nullptr; std::unique_ptr<cubeb_resampler_speex_one_way<T>> input_resampler = nullptr;
@ -528,35 +510,31 @@ cubeb_resampler_create_internal(cubeb_stream * stream,
sample rate, use a no-op resampler, that simply forwards the buffers to the sample rate, use a no-op resampler, that simply forwards the buffers to the
callback. */ callback. */
if (((input_params && input_params->rate == target_rate) && if (((input_params && input_params->rate == target_rate) &&
(output_params && output_params->rate == target_rate)) || (output_params && output_params->rate == target_rate)) ||
(input_params && !output_params && (input_params->rate == target_rate)) || (input_params && !output_params && (input_params->rate == target_rate)) ||
(output_params && !input_params && (output_params->rate == target_rate))) { (output_params && !input_params &&
(output_params->rate == target_rate))) {
LOG("Input and output sample-rate match, target rate of %dHz", target_rate); LOG("Input and output sample-rate match, target rate of %dHz", target_rate);
return new passthrough_resampler<T>(stream, callback, return new passthrough_resampler<T>(
user_ptr, stream, callback, user_ptr, input_params ? input_params->channels : 0,
input_params ? input_params->channels : 0, target_rate);
target_rate);
} }
/* Determine if we need to resampler one or both directions, and create the /* Determine if we need to resampler one or both directions, and create the
resamplers. */ resamplers. */
if (output_params && (output_params->rate != target_rate)) { if (output_params && (output_params->rate != target_rate)) {
output_resampler.reset( output_resampler.reset(new cubeb_resampler_speex_one_way<T>(
new cubeb_resampler_speex_one_way<T>(output_params->channels, output_params->channels, target_rate, output_params->rate,
target_rate, to_speex_quality(quality)));
output_params->rate,
to_speex_quality(quality)));
if (!output_resampler) { if (!output_resampler) {
return NULL; return NULL;
} }
} }
if (input_params && (input_params->rate != target_rate)) { if (input_params && (input_params->rate != target_rate)) {
input_resampler.reset( input_resampler.reset(new cubeb_resampler_speex_one_way<T>(
new cubeb_resampler_speex_one_way<T>(input_params->channels, input_params->channels, input_params->rate, target_rate,
input_params->rate, to_speex_quality(quality)));
target_rate,
to_speex_quality(quality)));
if (!input_resampler) { if (!input_resampler) {
return NULL; return NULL;
} }
@ -572,7 +550,8 @@ cubeb_resampler_create_internal(cubeb_stream * stream,
if (!output_delay) { if (!output_delay) {
return NULL; return NULL;
} }
} else if (output_resampler && !input_resampler && input_params && output_params) { } else if (output_resampler && !input_resampler && input_params &&
output_params) {
input_delay.reset(new delay_line<T>(output_resampler->latency(), input_delay.reset(new delay_line<T>(output_resampler->latency(),
input_params->channels, input_params->channels,
output_params->rate)); output_params->rate));
@ -582,29 +561,26 @@ cubeb_resampler_create_internal(cubeb_stream * stream,
} }
if (input_resampler && output_resampler) { if (input_resampler && output_resampler) {
LOG("Resampling input (%d) and output (%d) to target rate of %dHz", input_params->rate, output_params->rate, target_rate); LOG("Resampling input (%d) and output (%d) to target rate of %dHz",
return new cubeb_resampler_speex<T, input_params->rate, output_params->rate, target_rate);
cubeb_resampler_speex_one_way<T>, return new cubeb_resampler_speex<T, cubeb_resampler_speex_one_way<T>,
cubeb_resampler_speex_one_way<T>> cubeb_resampler_speex_one_way<T>>(
(input_resampler.release(), input_resampler.release(), output_resampler.release(), stream, callback,
output_resampler.release(), user_ptr);
stream, callback, user_ptr);
} else if (input_resampler) { } else if (input_resampler) {
LOG("Resampling input (%d) to target and output rate of %dHz", input_params->rate, target_rate); LOG("Resampling input (%d) to target and output rate of %dHz",
return new cubeb_resampler_speex<T, input_params->rate, target_rate);
cubeb_resampler_speex_one_way<T>, return new cubeb_resampler_speex<T, cubeb_resampler_speex_one_way<T>,
delay_line<T>> delay_line<T>>(input_resampler.release(),
(input_resampler.release(), output_delay.release(),
output_delay.release(), stream, callback, user_ptr);
stream, callback, user_ptr);
} else { } else {
LOG("Resampling output (%dHz) to target and input rate of %dHz", output_params->rate, target_rate); LOG("Resampling output (%dHz) to target and input rate of %dHz",
return new cubeb_resampler_speex<T, output_params->rate, target_rate);
delay_line<T>, return new cubeb_resampler_speex<T, delay_line<T>,
cubeb_resampler_speex_one_way<T>> cubeb_resampler_speex_one_way<T>>(
(input_delay.release(), input_delay.release(), output_resampler.release(), stream, callback,
output_resampler.release(), user_ptr);
stream, callback, user_ptr);
} }
} }

View file

@ -16,17 +16,16 @@
them in the correct order. */ them in the correct order. */
typedef struct { typedef struct {
AudioBuffer * buffer_array; /**< Array that hold pointers of the allocated space for the buffers. */ AudioBuffer * buffer_array; /**< Array that hold pointers of the allocated
unsigned int tail; /**< Index of the last element (first to deliver). */ space for the buffers. */
unsigned int count; /**< Number of elements in the array. */ unsigned int tail; /**< Index of the last element (first to deliver). */
unsigned int capacity; /**< Total length of the array. */ unsigned int count; /**< Number of elements in the array. */
unsigned int capacity; /**< Total length of the array. */
} ring_array; } ring_array;
static int static int
single_audiobuffer_init(AudioBuffer * buffer, single_audiobuffer_init(AudioBuffer * buffer, uint32_t bytesPerFrame,
uint32_t bytesPerFrame, uint32_t channelsPerFrame, uint32_t frames)
uint32_t channelsPerFrame,
uint32_t frames)
{ {
assert(buffer); assert(buffer);
assert(bytesPerFrame > 0 && channelsPerFrame && frames > 0); assert(bytesPerFrame > 0 && channelsPerFrame && frames > 0);
@ -36,7 +35,7 @@ single_audiobuffer_init(AudioBuffer * buffer,
if (buffer->mData == NULL) { if (buffer->mData == NULL) {
return CUBEB_ERROR; return CUBEB_ERROR;
} }
PodZero(static_cast<char*>(buffer->mData), size); PodZero(static_cast<char *>(buffer->mData), size);
buffer->mNumberChannels = channelsPerFrame; buffer->mNumberChannels = channelsPerFrame;
buffer->mDataByteSize = size; buffer->mDataByteSize = size;
@ -48,15 +47,12 @@ single_audiobuffer_init(AudioBuffer * buffer,
@param ra The ring_array pointer of allocated structure. @param ra The ring_array pointer of allocated structure.
@retval 0 on success. */ @retval 0 on success. */
int int
ring_array_init(ring_array * ra, ring_array_init(ring_array * ra, uint32_t capacity, uint32_t bytesPerFrame,
uint32_t capacity, uint32_t channelsPerFrame, uint32_t framesPerBuffer)
uint32_t bytesPerFrame,
uint32_t channelsPerFrame,
uint32_t framesPerBuffer)
{ {
assert(ra); assert(ra);
if (capacity == 0 || bytesPerFrame == 0 || if (capacity == 0 || bytesPerFrame == 0 || channelsPerFrame == 0 ||
channelsPerFrame == 0 || framesPerBuffer == 0) { framesPerBuffer == 0) {
return CUBEB_ERROR_INVALID_PARAMETER; return CUBEB_ERROR_INVALID_PARAMETER;
} }
ra->capacity = capacity; ra->capacity = capacity;
@ -70,8 +66,7 @@ ring_array_init(ring_array * ra,
} }
for (unsigned int i = 0; i < ra->capacity; ++i) { for (unsigned int i = 0; i < ra->capacity; ++i) {
if (single_audiobuffer_init(&ra->buffer_array[i], if (single_audiobuffer_init(&ra->buffer_array[i], bytesPerFrame,
bytesPerFrame,
channelsPerFrame, channelsPerFrame,
framesPerBuffer) != CUBEB_OK) { framesPerBuffer) != CUBEB_OK) {
return CUBEB_ERROR; return CUBEB_ERROR;
@ -87,7 +82,7 @@ void
ring_array_destroy(ring_array * ra) ring_array_destroy(ring_array * ra)
{ {
assert(ra); assert(ra);
if (ra->buffer_array == NULL){ if (ra->buffer_array == NULL) {
return; return;
} }
for (unsigned int i = 0; i < ra->capacity; ++i) { for (unsigned int i = 0; i < ra->capacity; ++i) {
@ -95,12 +90,13 @@ ring_array_destroy(ring_array * ra)
operator delete(ra->buffer_array[i].mData); operator delete(ra->buffer_array[i].mData);
} }
} }
delete [] ra->buffer_array; delete[] ra->buffer_array;
} }
/** Get the allocated buffer to be stored with fresh data. /** Get the allocated buffer to be stored with fresh data.
@param ra The ring_array pointer. @param ra The ring_array pointer.
@retval Pointer of the allocated space to be stored with fresh data or NULL if full. */ @retval Pointer of the allocated space to be stored with fresh data or NULL
if full. */
AudioBuffer * AudioBuffer *
ring_array_get_free_buffer(ring_array * ra) ring_array_get_free_buffer(ring_array * ra)
{ {
@ -156,4 +152,4 @@ ring_array_get_dummy_buffer(ring_array * ra)
return &ra->buffer_array[0]; return &ra->buffer_array[0];
} }
#endif //CUBEB_RING_ARRAY_H #endif // CUBEB_RING_ARRAY_H

View file

@ -18,10 +18,10 @@
/** /**
* Single producer single consumer lock-free and wait-free ring buffer. * Single producer single consumer lock-free and wait-free ring buffer.
* *
* This data structure allows producing data from one thread, and consuming it on * This data structure allows producing data from one thread, and consuming it
* another thread, safely and without explicit synchronization. If used on two * on another thread, safely and without explicit synchronization. If used on
* threads, this data structure uses atomics for thread safety. It is possible * two threads, this data structure uses atomics for thread safety. It is
* to disable the use of atomics at compile time and only use this data * possible to disable the use of atomics at compile time and only use this data
* structure on one thread. * structure on one thread.
* *
* The role for the producer and the consumer must be constant, i.e., the * The role for the producer and the consumer must be constant, i.e., the
@ -48,9 +48,7 @@
* providing an external buffer to copy into is an easy way to have linear * providing an external buffer to copy into is an easy way to have linear
* data for further processing. * data for further processing.
*/ */
template <typename T> template <typename T> class ring_buffer_base {
class ring_buffer_base
{
public: public:
/** /**
* Constructor for a ring buffer. * Constructor for a ring buffer.
@ -61,11 +59,10 @@ public:
* @param capacity The maximum number of element this ring buffer will hold. * @param capacity The maximum number of element this ring buffer will hold.
*/ */
ring_buffer_base(int capacity) ring_buffer_base(int capacity)
/* One more element to distinguish from empty and full buffer. */ /* One more element to distinguish from empty and full buffer. */
: capacity_(capacity + 1) : capacity_(capacity + 1)
{ {
assert(storage_capacity() < assert(storage_capacity() < std::numeric_limits<int>::max() / 2 &&
std::numeric_limits<int>::max() / 2 &&
"buffer too large for the type of index used."); "buffer too large for the type of index used.");
assert(capacity_ > 0); assert(capacity_ > 0);
@ -84,10 +81,7 @@ public:
* @param count The number of elements to enqueue. * @param count The number of elements to enqueue.
* @return The number of element enqueued. * @return The number of element enqueued.
*/ */
int enqueue_default(int count) int enqueue_default(int count) { return enqueue(nullptr, count); }
{
return enqueue(nullptr, count);
}
/** /**
* @brief Put an element in the queue * @brief Put an element in the queue
* *
@ -97,20 +91,18 @@ public:
* *
* @return 1 if the element was inserted, 0 otherwise. * @return 1 if the element was inserted, 0 otherwise.
*/ */
int enqueue(T& element) int enqueue(T & element) { return enqueue(&element, 1); }
{
return enqueue(&element, 1);
}
/** /**
* Push `count` elements in the ring buffer. * Push `count` elements in the ring buffer.
* *
* Only safely called on the producer thread. * Only safely called on the producer thread.
* *
* @param elements a pointer to a buffer containing at least `count` elements. * @param elements a pointer to a buffer containing at least `count` elements.
* If `elements` is nullptr, zero or default constructed elements are enqueued. * If `elements` is nullptr, zero or default constructed elements are
* enqueued.
* @param count The number of elements to read from `elements` * @param count The number of elements to read from `elements`
* @return The number of elements successfully coped from `elements` and inserted * @return The number of elements successfully coped from `elements` and
* into the ring buffer. * inserted into the ring buffer.
*/ */
int enqueue(T * elements, int count) int enqueue(T * elements, int count)
{ {
@ -118,19 +110,17 @@ public:
assert_correct_thread(producer_id); assert_correct_thread(producer_id);
#endif #endif
int rd_idx = read_index_.load(std::memory_order::memory_order_relaxed); int rd_idx = read_index_.load(std::memory_order_relaxed);
int wr_idx = write_index_.load(std::memory_order::memory_order_relaxed); int wr_idx = write_index_.load(std::memory_order_relaxed);
if (full_internal(rd_idx, wr_idx)) { if (full_internal(rd_idx, wr_idx)) {
return 0; return 0;
} }
int to_write = int to_write = std::min(available_write_internal(rd_idx, wr_idx), count);
std::min(available_write_internal(rd_idx, wr_idx), count);
/* First part, from the write index to the end of the array. */ /* First part, from the write index to the end of the array. */
int first_part = std::min(storage_capacity() - wr_idx, int first_part = std::min(storage_capacity() - wr_idx, to_write);
to_write);
/* Second part, from the beginning of the array */ /* Second part, from the beginning of the array */
int second_part = to_write - first_part; int second_part = to_write - first_part;
@ -142,7 +132,8 @@ public:
ConstructDefault(data_.get(), second_part); ConstructDefault(data_.get(), second_part);
} }
write_index_.store(increment_index(wr_idx, to_write), std::memory_order::memory_order_release); write_index_.store(increment_index(wr_idx, to_write),
std::memory_order_release);
return to_write; return to_write;
} }
@ -163,15 +154,14 @@ public:
assert_correct_thread(consumer_id); assert_correct_thread(consumer_id);
#endif #endif
int wr_idx = write_index_.load(std::memory_order::memory_order_acquire); int wr_idx = write_index_.load(std::memory_order_acquire);
int rd_idx = read_index_.load(std::memory_order::memory_order_relaxed); int rd_idx = read_index_.load(std::memory_order_relaxed);
if (empty_internal(rd_idx, wr_idx)) { if (empty_internal(rd_idx, wr_idx)) {
return 0; return 0;
} }
int to_read = int to_read = std::min(available_read_internal(rd_idx, wr_idx), count);
std::min(available_read_internal(rd_idx, wr_idx), count);
int first_part = std::min(storage_capacity() - rd_idx, to_read); int first_part = std::min(storage_capacity() - rd_idx, to_read);
int second_part = to_read - first_part; int second_part = to_read - first_part;
@ -181,7 +171,8 @@ public:
Copy(elements + first_part, data_.get(), second_part); Copy(elements + first_part, data_.get(), second_part);
} }
read_index_.store(increment_index(rd_idx, to_read), std::memory_order::memory_order_relaxed); read_index_.store(increment_index(rd_idx, to_read),
std::memory_order_relaxed);
return to_read; return to_read;
} }
@ -197,8 +188,9 @@ public:
#ifndef NDEBUG #ifndef NDEBUG
assert_correct_thread(consumer_id); assert_correct_thread(consumer_id);
#endif #endif
return available_read_internal(read_index_.load(std::memory_order::memory_order_relaxed), return available_read_internal(
write_index_.load(std::memory_order::memory_order_relaxed)); read_index_.load(std::memory_order_relaxed),
write_index_.load(std::memory_order_relaxed));
} }
/** /**
* Get the number of available elements for consuming. * Get the number of available elements for consuming.
@ -212,8 +204,9 @@ public:
#ifndef NDEBUG #ifndef NDEBUG
assert_correct_thread(producer_id); assert_correct_thread(producer_id);
#endif #endif
return available_write_internal(read_index_.load(std::memory_order::memory_order_relaxed), return available_write_internal(
write_index_.load(std::memory_order::memory_order_relaxed)); read_index_.load(std::memory_order_relaxed),
write_index_.load(std::memory_order_relaxed));
} }
/** /**
* Get the total capacity, for this ring buffer. * Get the total capacity, for this ring buffer.
@ -222,10 +215,7 @@ public:
* *
* @return The maximum capacity of this ring buffer. * @return The maximum capacity of this ring buffer.
*/ */
int capacity() const int capacity() const { return storage_capacity() - 1; }
{
return storage_capacity() - 1;
}
/** /**
* Reset the consumer and producer thread identifier, in case the thread are * Reset the consumer and producer thread identifier, in case the thread are
* being changed. This has to be externally synchronized. This is no-op when * being changed. This has to be externally synchronized. This is no-op when
@ -237,6 +227,7 @@ public:
consumer_id = producer_id = std::thread::id(); consumer_id = producer_id = std::thread::id();
#endif #endif
} }
private: private:
/** Return true if the ring buffer is empty. /** Return true if the ring buffer is empty.
* *
@ -244,8 +235,7 @@ private:
* @param write_index the write index to consider * @param write_index the write index to consider
* @return true if the ring buffer is empty, false otherwise. * @return true if the ring buffer is empty, false otherwise.
**/ **/
bool empty_internal(int read_index, bool empty_internal(int read_index, int write_index) const
int write_index) const
{ {
return write_index == read_index; return write_index == read_index;
} }
@ -258,8 +248,7 @@ private:
* @param write_index the write index to consider * @param write_index the write index to consider
* @return true if the ring buffer is full, false otherwise. * @return true if the ring buffer is full, false otherwise.
**/ **/
bool full_internal(int read_index, bool full_internal(int read_index, int write_index) const
int write_index) const
{ {
return (write_index + 1) % storage_capacity() == read_index; return (write_index + 1) % storage_capacity() == read_index;
} }
@ -269,18 +258,13 @@ private:
* *
* @return the number of elements that can be stored in the buffer. * @return the number of elements that can be stored in the buffer.
*/ */
int storage_capacity() const int storage_capacity() const { return capacity_; }
{
return capacity_;
}
/** /**
* Returns the number of elements available for reading. * Returns the number of elements available for reading.
* *
* @return the number of available elements for reading. * @return the number of available elements for reading.
*/ */
int int available_read_internal(int read_index, int write_index) const
available_read_internal(int read_index,
int write_index) const
{ {
if (write_index >= read_index) { if (write_index >= read_index) {
return write_index - read_index; return write_index - read_index;
@ -293,9 +277,7 @@ private:
* *
* @return the number of elements that can be written into the array. * @return the number of elements that can be written into the array.
*/ */
int int available_write_internal(int read_index, int write_index) const
available_write_internal(int read_index,
int write_index) const
{ {
/* We substract one element here to always keep at least one sample /* We substract one element here to always keep at least one sample
* free in the buffer, to distinguish between full and empty array. */ * free in the buffer, to distinguish between full and empty array. */
@ -312,8 +294,7 @@ private:
* @param increment the number by which `index` is incremented. * @param increment the number by which `index` is incremented.
* @return the new index. * @return the new index.
*/ */
int int increment_index(int index, int increment) const
increment_index(int index, int increment) const
{ {
assert(increment >= 0); assert(increment >= 0);
return (index + increment) % storage_capacity(); return (index + increment) % storage_capacity();
@ -325,7 +306,7 @@ private:
* @param id the id of the thread that has called the calling method first. * @param id the id of the thread that has called the calling method first.
*/ */
#ifndef NDEBUG #ifndef NDEBUG
static void assert_correct_thread(std::thread::id& id) static void assert_correct_thread(std::thread::id & id)
{ {
if (id == std::thread::id()) { if (id == std::thread::id()) {
id = std::this_thread::get_id(); id = std::this_thread::get_id();
@ -354,9 +335,7 @@ private:
/** /**
* Adapter for `ring_buffer_base` that exposes an interface in frames. * Adapter for `ring_buffer_base` that exposes an interface in frames.
*/ */
template <typename T> template <typename T> class audio_ring_buffer_base {
class audio_ring_buffer_base
{
public: public:
/** /**
* @brief Constructor. * @brief Constructor.
@ -365,8 +344,8 @@ public:
* @param capacity_in_frames The capacity in frames. * @param capacity_in_frames The capacity in frames.
*/ */
audio_ring_buffer_base(int channel_count, int capacity_in_frames) audio_ring_buffer_base(int channel_count, int capacity_in_frames)
: channel_count(channel_count) : channel_count(channel_count),
, ring_buffer(frames_to_samples(capacity_in_frames)) ring_buffer(frames_to_samples(capacity_in_frames))
{ {
assert(channel_count > 0); assert(channel_count > 0);
} }
@ -380,7 +359,8 @@ public:
*/ */
int enqueue_default(int frame_count) int enqueue_default(int frame_count)
{ {
return samples_to_frames(ring_buffer.enqueue(nullptr, frames_to_samples(frame_count))); return samples_to_frames(
ring_buffer.enqueue(nullptr, frames_to_samples(frame_count)));
} }
/** /**
* @brief Enqueue `frames_count` frames of audio. * @brief Enqueue `frames_count` frames of audio.
@ -396,7 +376,8 @@ public:
int enqueue(T * frames, int frame_count) int enqueue(T * frames, int frame_count)
{ {
return samples_to_frames(ring_buffer.enqueue(frames, frames_to_samples(frame_count))); return samples_to_frames(
ring_buffer.enqueue(frames, frames_to_samples(frame_count)));
} }
/** /**
@ -413,7 +394,8 @@ public:
*/ */
int dequeue(T * frames, int frame_count) int dequeue(T * frames, int frame_count)
{ {
return samples_to_frames(ring_buffer.dequeue(frames, frames_to_samples(frame_count))); return samples_to_frames(
ring_buffer.dequeue(frames, frames_to_samples(frame_count)));
} }
/** /**
* Get the number of available frames of audio for consuming. * Get the number of available frames of audio for consuming.
@ -444,10 +426,8 @@ public:
* *
* @return The maximum capacity of this ring buffer. * @return The maximum capacity of this ring buffer.
*/ */
int capacity() const int capacity() const { return samples_to_frames(ring_buffer.capacity()); }
{
return samples_to_frames(ring_buffer.capacity());
}
private: private:
/** /**
* @brief Frames to samples conversion. * @brief Frames to samples conversion.
@ -456,10 +436,7 @@ private:
* *
* @return A number of samples. * @return A number of samples.
*/ */
int frames_to_samples(int frames) const int frames_to_samples(int frames) const { return frames * channel_count; }
{
return frames * channel_count;
}
/** /**
* @brief Samples to frames conversion. * @brief Samples to frames conversion.
* *
@ -467,10 +444,7 @@ private:
* *
* @return A number of frames. * @return A number of frames.
*/ */
int samples_to_frames(int samples) const int samples_to_frames(int samples) const { return samples / channel_count; }
{
return samples / channel_count;
}
/** Number of channels of audio that will stream through this ring buffer. */ /** Number of channels of audio that will stream through this ring buffer. */
int channel_count; int channel_count;
/** The underlying ring buffer that is used to store the data. */ /** The underlying ring buffer that is used to store the data. */
@ -482,14 +456,13 @@ private:
* from two threads, one producer, one consumer (that never change role), * from two threads, one producer, one consumer (that never change role),
* without explicit synchronization. * without explicit synchronization.
*/ */
template<typename T> template <typename T> using lock_free_queue = ring_buffer_base<T>;
using lock_free_queue = ring_buffer_base<T>;
/** /**
* Lock-free instantiation of the `audio_ring_buffer` type. This is safe to use * Lock-free instantiation of the `audio_ring_buffer` type. This is safe to use
* from two threads, one producer, one consumer (that never change role), * from two threads, one producer, one consumer (that never change role),
* without explicit synchronization. * without explicit synchronization.
*/ */
template<typename T> template <typename T>
using lock_free_audio_ring_buffer = audio_ring_buffer_base<T>; using lock_free_audio_ring_buffer = audio_ring_buffer_base<T>;
#endif // CUBEB_RING_BUFFER_H #endif // CUBEB_RING_BUFFER_H

View file

@ -4,44 +4,46 @@
* This program is made available under an ISC-style license. See the * This program is made available under an ISC-style license. See the
* accompanying file LICENSE for details. * accompanying file LICENSE for details.
*/ */
#include "cubeb-internal.h"
#include "cubeb/cubeb.h"
#include <assert.h>
#include <dlfcn.h>
#include <inttypes.h> #include <inttypes.h>
#include <math.h> #include <math.h>
#include <poll.h> #include <poll.h>
#include <pthread.h> #include <pthread.h>
#include <sndio.h> #include <sndio.h>
#include <stdbool.h> #include <stdbool.h>
#include <stdlib.h>
#include <stdio.h> #include <stdio.h>
#include <dlfcn.h> #include <stdlib.h>
#include <assert.h>
#include "cubeb/cubeb.h"
#include "cubeb-internal.h"
#if defined(CUBEB_SNDIO_DEBUG) #if defined(CUBEB_SNDIO_DEBUG)
#define DPR(...) fprintf(stderr, __VA_ARGS__); #define DPR(...) fprintf(stderr, __VA_ARGS__);
#else #else
#define DPR(...) do {} while(0) #define DPR(...) \
do { \
} while (0)
#endif #endif
#ifdef DISABLE_LIBSNDIO_DLOPEN #ifdef DISABLE_LIBSNDIO_DLOPEN
#define WRAP(x) x #define WRAP(x) x
#else #else
#define WRAP(x) cubeb_##x #define WRAP(x) (*cubeb_##x)
#define LIBSNDIO_API_VISIT(X) \ #define LIBSNDIO_API_VISIT(X) \
X(sio_close) \ X(sio_close) \
X(sio_eof) \ X(sio_eof) \
X(sio_getpar) \ X(sio_getpar) \
X(sio_initpar) \ X(sio_initpar) \
X(sio_nfds) \ X(sio_nfds) \
X(sio_onmove) \ X(sio_onmove) \
X(sio_open) \ X(sio_open) \
X(sio_pollfd) \ X(sio_pollfd) \
X(sio_read) \ X(sio_read) \
X(sio_revents) \ X(sio_revents) \
X(sio_setpar) \ X(sio_setpar) \
X(sio_start) \ X(sio_start) \
X(sio_stop) \ X(sio_stop) \
X(sio_write) \ X(sio_write)
#define MAKE_TYPEDEF(x) static typeof(x) * cubeb_##x; #define MAKE_TYPEDEF(x) static typeof(x) * cubeb_##x;
LIBSNDIO_API_VISIT(MAKE_TYPEDEF); LIBSNDIO_API_VISIT(MAKE_TYPEDEF);
@ -58,33 +60,33 @@ struct cubeb {
struct cubeb_stream { struct cubeb_stream {
/* Note: Must match cubeb_stream layout in cubeb.c. */ /* Note: Must match cubeb_stream layout in cubeb.c. */
cubeb * context; cubeb * context;
void * arg; /* user arg to {data,state}_cb */ void * arg; /* user arg to {data,state}_cb */
/**/ /**/
pthread_t th; /* to run real-time audio i/o */ pthread_t th; /* to run real-time audio i/o */
pthread_mutex_t mtx; /* protects hdl and pos */ pthread_mutex_t mtx; /* protects hdl and pos */
struct sio_hdl *hdl; /* link us to sndio */ struct sio_hdl * hdl; /* link us to sndio */
int mode; /* bitmap of SIO_{PLAY,REC} */ int mode; /* bitmap of SIO_{PLAY,REC} */
int active; /* cubec_start() called */ int active; /* cubec_start() called */
int conv; /* need float->s16 conversion */ int conv; /* need float->s16 conversion */
unsigned char *rbuf; /* rec data consumed from here */ unsigned char * rbuf; /* rec data consumed from here */
unsigned char *pbuf; /* play data is prepared here */ unsigned char * pbuf; /* play data is prepared here */
unsigned int nfr; /* number of frames in ibuf and obuf */ unsigned int nfr; /* number of frames in ibuf and obuf */
unsigned int rbpf; /* rec bytes per frame */ unsigned int rbpf; /* rec bytes per frame */
unsigned int pbpf; /* play bytes per frame */ unsigned int pbpf; /* play bytes per frame */
unsigned int rchan; /* number of rec channels */ unsigned int rchan; /* number of rec channels */
unsigned int pchan; /* number of play channels */ unsigned int pchan; /* number of play channels */
unsigned int nblks; /* number of blocks in the buffer */ unsigned int nblks; /* number of blocks in the buffer */
uint64_t hwpos; /* frame number Joe hears right now */ uint64_t hwpos; /* frame number Joe hears right now */
uint64_t swpos; /* number of frames produced/consumed */ uint64_t swpos; /* number of frames produced/consumed */
cubeb_data_callback data_cb; /* cb to preapare data */ cubeb_data_callback data_cb; /* cb to preapare data */
cubeb_state_callback state_cb; /* cb to notify about state changes */ cubeb_state_callback state_cb; /* cb to notify about state changes */
float volume; /* current volume */ float volume; /* current volume */
}; };
static void static void
s16_setvol(void *ptr, long nsamp, float volume) s16_setvol(void * ptr, long nsamp, float volume)
{ {
int16_t *dst = ptr; int16_t * dst = ptr;
int32_t mult = volume * 32768; int32_t mult = volume * 32768;
int32_t s; int32_t s;
@ -96,10 +98,10 @@ s16_setvol(void *ptr, long nsamp, float volume)
} }
static void static void
float_to_s16(void *ptr, long nsamp, float volume) float_to_s16(void * ptr, long nsamp, float volume)
{ {
int16_t *dst = ptr; int16_t * dst = ptr;
float *src = ptr; float * src = ptr;
float mult = volume * 32768; float mult = volume * 32768;
int s; int s;
@ -114,10 +116,10 @@ float_to_s16(void *ptr, long nsamp, float volume)
} }
static void static void
s16_to_float(void *ptr, long nsamp) s16_to_float(void * ptr, long nsamp)
{ {
int16_t *src = ptr; int16_t * src = ptr;
float *dst = ptr; float * dst = ptr;
src += nsamp; src += nsamp;
dst += nsamp; dst += nsamp;
@ -133,9 +135,9 @@ sndio_get_device()
* On other platforms default to sndio devices, * On other platforms default to sndio devices,
* so cubebs other backends can be used instead. * so cubebs other backends can be used instead.
*/ */
const char *dev = getenv("AUDIODEVICE"); const char * dev = getenv("AUDIODEVICE");
if (dev == NULL || *dev == '\0') if (dev == NULL || *dev == '\0')
return "snd/0"; return "snd/0";
return dev; return dev;
#else #else
return SIO_DEVANY; return SIO_DEVANY;
@ -143,26 +145,26 @@ sndio_get_device()
} }
static void static void
sndio_onmove(void *arg, int delta) sndio_onmove(void * arg, int delta)
{ {
cubeb_stream *s = (cubeb_stream *)arg; cubeb_stream * s = (cubeb_stream *)arg;
s->hwpos += delta; s->hwpos += delta;
} }
static void * static void *
sndio_mainloop(void *arg) sndio_mainloop(void * arg)
{ {
struct pollfd *pfds; struct pollfd * pfds;
cubeb_stream *s = arg; cubeb_stream * s = arg;
int n, eof = 0, prime, nfds, events, revents, state = CUBEB_STATE_STARTED; int n, eof = 0, prime, nfds, events, revents, state = CUBEB_STATE_STARTED;
size_t pstart = 0, pend = 0, rstart = 0, rend = 0; size_t pstart = 0, pend = 0, rstart = 0, rend = 0;
long nfr; long nfr;
nfds = WRAP(sio_nfds)(s->hdl); nfds = WRAP(sio_nfds)(s->hdl);
pfds = calloc(nfds, sizeof (struct pollfd)); pfds = calloc(nfds, sizeof(struct pollfd));
if (pfds == NULL) if (pfds == NULL)
return NULL; return NULL;
DPR("sndio_mainloop()\n"); DPR("sndio_mainloop()\n");
s->state_cb(s, s->arg, CUBEB_STATE_STARTED); s->state_cb(s, s->arg, CUBEB_STATE_STARTED);
@ -196,7 +198,7 @@ sndio_mainloop(void *arg)
/* do we have a complete block? */ /* do we have a complete block? */
if ((!(s->mode & SIO_PLAY) || pstart == pend) && if ((!(s->mode & SIO_PLAY) || pstart == pend) &&
(!(s->mode & SIO_REC) || rstart == rend)) { (!(s->mode & SIO_REC) || rstart == rend)) {
if (eof) { if (eof) {
DPR("sndio_mainloop() drained\n"); DPR("sndio_mainloop() drained\n");
@ -224,7 +226,7 @@ sndio_mainloop(void *arg)
if (!(s->mode & SIO_PLAY) || nfr == 0) { if (!(s->mode & SIO_PLAY) || nfr == 0) {
state = CUBEB_STATE_DRAINED; state = CUBEB_STATE_DRAINED;
break; break;
} }
/* need to write (aka drain) the partial play block we got */ /* need to write (aka drain) the partial play block we got */
pend = nfr * s->pbpf; pend = nfr * s->pbpf;
@ -302,10 +304,10 @@ sndio_mainloop(void *arg)
} }
/*static*/ int /*static*/ int
sndio_init(cubeb **context, char const *context_name) sndio_init(cubeb ** context, char const * context_name)
{ {
void * libsndio = NULL; void * libsndio = NULL;
struct sio_hdl *hdl; struct sio_hdl * hdl;
assert(context); assert(context);
@ -319,13 +321,14 @@ sndio_init(cubeb **context, char const *context_name)
} }
} }
#define LOAD(x) { \ #define LOAD(x) \
cubeb_##x = dlsym(libsndio, #x); \ { \
if (!cubeb_##x) { \ cubeb_##x = dlsym(libsndio, #x); \
DPR("sndio_init(%s) failed dlsym(%s)\n", context_name, #x); \ if (!cubeb_##x) { \
dlclose(libsndio); \ DPR("sndio_init(%s) failed dlsym(%s)\n", context_name, #x); \
return CUBEB_ERROR; \ dlclose(libsndio); \
} \ return CUBEB_ERROR; \
} \
} }
LIBSNDIO_API_VISIT(LOAD); LIBSNDIO_API_VISIT(LOAD);
@ -342,7 +345,7 @@ sndio_init(cubeb **context, char const *context_name)
DPR("sndio_init(%s)\n", context_name); DPR("sndio_init(%s)\n", context_name);
*context = malloc(sizeof(**context)); *context = malloc(sizeof(**context));
if (*context == NULL) if (*context == NULL)
return CUBEB_ERROR; return CUBEB_ERROR;
(*context)->libsndio = libsndio; (*context)->libsndio = libsndio;
(*context)->ops = &sndio_ops; (*context)->ops = &sndio_ops;
(void)context_name; (void)context_name;
@ -350,13 +353,13 @@ sndio_init(cubeb **context, char const *context_name)
} }
static char const * static char const *
sndio_get_backend_id(cubeb *context) sndio_get_backend_id(cubeb * context)
{ {
return "sndio"; return "sndio";
} }
static void static void
sndio_destroy(cubeb *context) sndio_destroy(cubeb * context)
{ {
DPR("sndio_destroy()\n"); DPR("sndio_destroy()\n");
if (context->libsndio) if (context->libsndio)
@ -365,19 +368,16 @@ sndio_destroy(cubeb *context)
} }
static int static int
sndio_stream_init(cubeb * context, sndio_stream_init(cubeb * context, cubeb_stream ** stream,
cubeb_stream ** stream, char const * stream_name, cubeb_devid input_device,
char const * stream_name,
cubeb_devid input_device,
cubeb_stream_params * input_stream_params, cubeb_stream_params * input_stream_params,
cubeb_devid output_device, cubeb_devid output_device,
cubeb_stream_params * output_stream_params, cubeb_stream_params * output_stream_params,
unsigned int latency_frames, unsigned int latency_frames,
cubeb_data_callback data_callback, cubeb_data_callback data_callback,
cubeb_state_callback state_callback, cubeb_state_callback state_callback, void * user_ptr)
void *user_ptr)
{ {
cubeb_stream *s; cubeb_stream * s;
struct sio_par wpar, rpar; struct sio_par wpar, rpar;
cubeb_sample_format format; cubeb_sample_format format;
int rate; int rate;
@ -445,8 +445,8 @@ sndio_stream_init(cubeb * context,
DPR("sndio_stream_init(), sio_setpar() failed\n"); DPR("sndio_stream_init(), sio_setpar() failed\n");
goto err; goto err;
} }
if (rpar.bits != wpar.bits || rpar.le != wpar.le || if (rpar.bits != wpar.bits || rpar.le != wpar.le || rpar.sig != wpar.sig ||
rpar.sig != wpar.sig || rpar.rate != wpar.rate || rpar.rate != wpar.rate ||
((s->mode & SIO_REC) && rpar.rchan != wpar.rchan) || ((s->mode & SIO_REC) && rpar.rchan != wpar.rchan) ||
((s->mode & SIO_PLAY) && rpar.pchan != wpar.pchan)) { ((s->mode & SIO_PLAY) && rpar.pchan != wpar.pchan)) {
DPR("sndio_stream_init() unsupported params\n"); DPR("sndio_stream_init() unsupported params\n");
@ -522,7 +522,8 @@ sndio_get_preferred_sample_rate(cubeb * ctx, uint32_t * rate)
} }
static int static int
sndio_get_min_latency(cubeb * ctx, cubeb_stream_params params, uint32_t * latency_frames) sndio_get_min_latency(cubeb * ctx, cubeb_stream_params params,
uint32_t * latency_frames)
{ {
/* /*
* We've no device-independent minimum latency. * We've no device-independent minimum latency.
@ -533,7 +534,7 @@ sndio_get_min_latency(cubeb * ctx, cubeb_stream_params params, uint32_t * latenc
} }
static void static void
sndio_stream_destroy(cubeb_stream *s) sndio_stream_destroy(cubeb_stream * s)
{ {
DPR("sndio_stream_destroy()\n"); DPR("sndio_stream_destroy()\n");
WRAP(sio_close)(s->hdl); WRAP(sio_close)(s->hdl);
@ -545,7 +546,7 @@ sndio_stream_destroy(cubeb_stream *s)
} }
static int static int
sndio_stream_start(cubeb_stream *s) sndio_stream_start(cubeb_stream * s)
{ {
int err; int err;
@ -560,9 +561,9 @@ sndio_stream_start(cubeb_stream *s)
} }
static int static int
sndio_stream_stop(cubeb_stream *s) sndio_stream_stop(cubeb_stream * s)
{ {
void *dummy; void * dummy;
DPR("sndio_stream_stop()\n"); DPR("sndio_stream_stop()\n");
if (s->active) { if (s->active) {
@ -573,7 +574,7 @@ sndio_stream_stop(cubeb_stream *s)
} }
static int static int
sndio_stream_get_position(cubeb_stream *s, uint64_t *p) sndio_stream_get_position(cubeb_stream * s, uint64_t * p)
{ {
pthread_mutex_lock(&s->mtx); pthread_mutex_lock(&s->mtx);
DPR("sndio_stream_get_position() %" PRId64 "\n", s->hwpos); DPR("sndio_stream_get_position() %" PRId64 "\n", s->hwpos);
@ -583,7 +584,7 @@ sndio_stream_get_position(cubeb_stream *s, uint64_t *p)
} }
static int static int
sndio_stream_set_volume(cubeb_stream *s, float volume) sndio_stream_set_volume(cubeb_stream * s, float volume)
{ {
DPR("sndio_stream_set_volume(%f)\n", volume); DPR("sndio_stream_set_volume(%f)\n", volume);
pthread_mutex_lock(&s->mtx); pthread_mutex_lock(&s->mtx);
@ -606,27 +607,27 @@ sndio_stream_get_latency(cubeb_stream * stm, uint32_t * latency)
} }
static int static int
sndio_enumerate_devices(cubeb *context, cubeb_device_type type, sndio_enumerate_devices(cubeb * context, cubeb_device_type type,
cubeb_device_collection *collection) cubeb_device_collection * collection)
{ {
static char dev[] = SIO_DEVANY; static char dev[] = SIO_DEVANY;
cubeb_device_info *device; cubeb_device_info * device;
device = malloc(sizeof(cubeb_device_info)); device = malloc(sizeof(cubeb_device_info));
if (device == NULL) if (device == NULL)
return CUBEB_ERROR; return CUBEB_ERROR;
device->devid = dev; /* passed to stream_init() */ device->devid = dev; /* passed to stream_init() */
device->device_id = dev; /* printable in UI */ device->device_id = dev; /* printable in UI */
device->friendly_name = dev; /* same, but friendly */ device->friendly_name = dev; /* same, but friendly */
device->group_id = dev; /* actual device if full-duplex */ device->group_id = dev; /* actual device if full-duplex */
device->vendor_name = NULL; /* may be NULL */ device->vendor_name = NULL; /* may be NULL */
device->type = type; /* Input/Output */ device->type = type; /* Input/Output */
device->state = CUBEB_DEVICE_STATE_ENABLED; device->state = CUBEB_DEVICE_STATE_ENABLED;
device->preferred = CUBEB_DEVICE_PREF_ALL; device->preferred = CUBEB_DEVICE_PREF_ALL;
device->format = CUBEB_DEVICE_FMT_S16NE; device->format = CUBEB_DEVICE_FMT_S16NE;
device->default_format = CUBEB_DEVICE_FMT_S16NE; device->default_format = CUBEB_DEVICE_FMT_S16NE;
device->max_channels = 16; device->max_channels = (type == CUBEB_DEVICE_TYPE_INPUT) ? 2 : 8;
device->default_rate = 48000; device->default_rate = 48000;
device->min_rate = 4000; device->min_rate = 4000;
device->max_rate = 192000; device->max_rate = 192000;
@ -639,32 +640,30 @@ sndio_enumerate_devices(cubeb *context, cubeb_device_type type,
static int static int
sndio_device_collection_destroy(cubeb * context, sndio_device_collection_destroy(cubeb * context,
cubeb_device_collection * collection) cubeb_device_collection * collection)
{ {
free(collection->device); free(collection->device);
return CUBEB_OK; return CUBEB_OK;
} }
static struct cubeb_ops const sndio_ops = { static struct cubeb_ops const sndio_ops = {
.init = sndio_init, .init = sndio_init,
.get_backend_id = sndio_get_backend_id, .get_backend_id = sndio_get_backend_id,
.get_max_channel_count = sndio_get_max_channel_count, .get_max_channel_count = sndio_get_max_channel_count,
.get_min_latency = sndio_get_min_latency, .get_min_latency = sndio_get_min_latency,
.get_preferred_sample_rate = sndio_get_preferred_sample_rate, .get_preferred_sample_rate = sndio_get_preferred_sample_rate,
.enumerate_devices = sndio_enumerate_devices, .enumerate_devices = sndio_enumerate_devices,
.device_collection_destroy = sndio_device_collection_destroy, .device_collection_destroy = sndio_device_collection_destroy,
.destroy = sndio_destroy, .destroy = sndio_destroy,
.stream_init = sndio_stream_init, .stream_init = sndio_stream_init,
.stream_destroy = sndio_stream_destroy, .stream_destroy = sndio_stream_destroy,
.stream_start = sndio_stream_start, .stream_start = sndio_stream_start,
.stream_stop = sndio_stream_stop, .stream_stop = sndio_stream_stop,
.stream_reset_default_device = NULL, .stream_get_position = sndio_stream_get_position,
.stream_get_position = sndio_stream_get_position, .stream_get_latency = sndio_stream_get_latency,
.stream_get_latency = sndio_stream_get_latency, .stream_set_volume = sndio_stream_set_volume,
.stream_set_volume = sndio_stream_set_volume, .stream_set_name = NULL,
.stream_set_name = NULL, .stream_get_current_device = NULL,
.stream_get_current_device = NULL, .stream_device_destroy = NULL,
.stream_device_destroy = NULL, .stream_register_device_changed_callback = NULL,
.stream_register_device_changed_callback = NULL, .register_device_collection_changed = NULL};
.register_device_collection_changed = NULL
};

View file

@ -23,7 +23,7 @@ struct cubeb_strings {
int int
cubeb_strings_init(cubeb_strings ** strings) cubeb_strings_init(cubeb_strings ** strings)
{ {
cubeb_strings* strs = NULL; cubeb_strings * strs = NULL;
if (!strings) { if (!strings) {
return CUBEB_ERROR; return CUBEB_ERROR;
@ -58,7 +58,7 @@ cubeb_strings_destroy(cubeb_strings * strings)
sp = strings->data; sp = strings->data;
se = sp + strings->count; se = sp + strings->count;
for ( ; sp != se; sp++) { for (; sp != se; sp++) {
if (*sp) { if (*sp) {
free(*sp); free(*sp);
} }
@ -88,7 +88,7 @@ cubeb_strings_lookup(cubeb_strings * strings, char const * s)
sp = strings->data; sp = strings->data;
se = sp + strings->count; se = sp + strings->count;
for ( ; sp != se; sp++) { for (; sp != se; sp++) {
if (*sp && strcmp(*sp, s) == 0) { if (*sp && strcmp(*sp, s) == 0) {
return *sp; return *sp;
} }
@ -152,4 +152,3 @@ cubeb_strings_intern(cubeb_strings * strings, char const * s)
return cubeb_strings_push(strings, s); return cubeb_strings_push(strings, s);
} }

View file

@ -22,12 +22,14 @@ typedef struct cubeb_strings cubeb_strings;
interned string storage will be returned. interned string storage will be returned.
@retval CUBEB_OK in case of success. @retval CUBEB_OK in case of success.
@retval CUBEB_ERROR in case of error. */ @retval CUBEB_ERROR in case of error. */
CUBEB_EXPORT int cubeb_strings_init(cubeb_strings ** strings); CUBEB_EXPORT int
cubeb_strings_init(cubeb_strings ** strings);
/** Destroy an interned string structure freeing all associated memory. /** Destroy an interned string structure freeing all associated memory.
@param strings An opaque pointer to the interned string storage to @param strings An opaque pointer to the interned string storage to
destroy. */ destroy. */
CUBEB_EXPORT void cubeb_strings_destroy(cubeb_strings * strings); CUBEB_EXPORT void
cubeb_strings_destroy(cubeb_strings * strings);
/** Add string to internal storage. /** Add string to internal storage.
@param strings Opaque pointer to interned string storage. @param strings Opaque pointer to interned string storage.
@ -35,7 +37,8 @@ CUBEB_EXPORT void cubeb_strings_destroy(cubeb_strings * strings);
@retval CUBEB_OK @retval CUBEB_OK
@retval CUBEB_ERROR @retval CUBEB_ERROR
*/ */
CUBEB_EXPORT char const * cubeb_strings_intern(cubeb_strings * strings, char const * s); CUBEB_EXPORT char const *
cubeb_strings_intern(cubeb_strings * strings, char const * s);
#if defined(__cplusplus) #if defined(__cplusplus)
} }

View file

@ -4,18 +4,18 @@
* This program is made available under an ISC-style license. See the * This program is made available under an ISC-style license. See the
* accompanying file LICENSE for details. * accompanying file LICENSE for details.
*/ */
#include <sys/audioio.h> #include "cubeb-internal.h"
#include <sys/ioctl.h> #include "cubeb/cubeb.h"
#include <fcntl.h> #include <fcntl.h>
#include <unistd.h> #include <limits.h>
#include <pthread.h> #include <pthread.h>
#include <stdbool.h> #include <stdbool.h>
#include <stdlib.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h>
#include <string.h> #include <string.h>
#include <limits.h> #include <sys/audioio.h>
#include "cubeb/cubeb.h" #include <sys/ioctl.h>
#include "cubeb-internal.h" #include <unistd.h>
/* Default to 4 + 1 for the default device. */ /* Default to 4 + 1 for the default device. */
#ifndef SUN_DEVICE_COUNT #ifndef SUN_DEVICE_COUNT
@ -45,11 +45,11 @@
*/ */
#ifndef SUN_MAX_CHANNELS #ifndef SUN_MAX_CHANNELS
# ifdef __NetBSD__ #ifdef __NetBSD__
# define SUN_MAX_CHANNELS (12) #define SUN_MAX_CHANNELS (12)
# else #else
# define SUN_MAX_CHANNELS (2) #define SUN_MAX_CHANNELS (2)
# endif #endif
#endif #endif
#ifndef SUN_MIN_RATE #ifndef SUN_MIN_RATE
@ -145,8 +145,8 @@ sun_get_min_latency(cubeb * context, cubeb_stream_params params,
} }
static int static int
sun_get_hwinfo(const char * device, struct audio_info * format, sun_get_hwinfo(const char * device, struct audio_info * format, int * props,
int * props, struct audio_device * dev) struct audio_device * dev)
{ {
int fd = -1; int fd = -1;
@ -181,9 +181,10 @@ error:
static int static int
sun_prinfo_verify_sanity(struct audio_prinfo * prinfo) sun_prinfo_verify_sanity(struct audio_prinfo * prinfo)
{ {
return prinfo->precision >= 8 && prinfo->precision <= 32 && return prinfo->precision >= 8 && prinfo->precision <= 32 &&
prinfo->channels >= 1 && prinfo->channels < SUN_MAX_CHANNELS && prinfo->channels >= 1 && prinfo->channels < SUN_MAX_CHANNELS &&
prinfo->sample_rate < SUN_MAX_RATE && prinfo->sample_rate > SUN_MIN_RATE; prinfo->sample_rate < SUN_MAX_RATE &&
prinfo->sample_rate > SUN_MIN_RATE;
} }
static int static int
@ -196,7 +197,7 @@ sun_enumerate_devices(cubeb * context, cubeb_device_type type,
char dev_friendly[64]; char dev_friendly[64];
struct audio_info hwfmt; struct audio_info hwfmt;
struct audio_device hwname; struct audio_device hwname;
struct audio_prinfo *prinfo = NULL; struct audio_prinfo * prinfo = NULL;
int hwprops; int hwprops;
collection->device = calloc(SUN_DEVICE_COUNT, sizeof(cubeb_device_info)); collection->device = calloc(SUN_DEVICE_COUNT, sizeof(cubeb_device_info));
@ -262,7 +263,8 @@ sun_enumerate_devices(cubeb * context, cubeb_device_type type,
device.vendor_name = strdup(hwname.name); device.vendor_name = strdup(hwname.name);
device.type = type; device.type = type;
device.state = CUBEB_DEVICE_STATE_ENABLED; device.state = CUBEB_DEVICE_STATE_ENABLED;
device.preferred = (i == 0) ? CUBEB_DEVICE_PREF_ALL : CUBEB_DEVICE_PREF_NONE; device.preferred =
(i == 0) ? CUBEB_DEVICE_PREF_ALL : CUBEB_DEVICE_PREF_NONE;
#ifdef AUDIO_GETFORMAT #ifdef AUDIO_GETFORMAT
device.max_channels = prinfo->channels; device.max_channels = prinfo->channels;
device.default_rate = prinfo->sample_rate; device.default_rate = prinfo->sample_rate;
@ -418,7 +420,7 @@ sun_linear16_set_vol(int16_t * buf, unsigned sample_count, float vol)
static void * static void *
sun_io_routine(void * arg) sun_io_routine(void * arg)
{ {
cubeb_stream *s = arg; cubeb_stream * s = arg;
cubeb_state state = CUBEB_STATE_STARTED; cubeb_state state = CUBEB_STATE_STARTED;
size_t to_read = 0; size_t to_read = 0;
long to_write = 0; long to_write = 0;
@ -439,8 +441,8 @@ sun_io_routine(void * arg)
sun_linear32_to_float(s->record.buf, sun_linear32_to_float(s->record.buf,
s->record.info.record.channels * SUN_BUFFER_FRAMES); s->record.info.record.channels * SUN_BUFFER_FRAMES);
} }
to_write = s->data_cb(s, s->user_ptr, to_write = s->data_cb(s, s->user_ptr, s->record.buf, s->play.buf,
s->record.buf, s->play.buf, SUN_BUFFER_FRAMES); SUN_BUFFER_FRAMES);
if (to_write == CUBEB_ERROR) { if (to_write == CUBEB_ERROR) {
state = CUBEB_STATE_ERROR; state = CUBEB_STATE_ERROR;
break; break;
@ -456,8 +458,8 @@ sun_io_routine(void * arg)
sun_float_to_linear32(s->play.buf, sun_float_to_linear32(s->play.buf,
s->play.info.play.channels * to_write, vol); s->play.info.play.channels * to_write, vol);
} else { } else {
sun_linear16_set_vol(s->play.buf, sun_linear16_set_vol(s->play.buf, s->play.info.play.channels * to_write,
s->play.info.play.channels * to_write, vol); vol);
} }
} }
if (to_write < SUN_BUFFER_FRAMES) { if (to_write < SUN_BUFFER_FRAMES) {
@ -473,7 +475,8 @@ sun_io_routine(void * arg)
if (to_write > 0) { if (to_write > 0) {
bytes = to_write * s->play.frame_size; bytes = to_write * s->play.frame_size;
if ((n = write(s->play.fd, (uint8_t *)s->play.buf + write_ofs, bytes)) < 0) { if ((n = write(s->play.fd, (uint8_t *)s->play.buf + write_ofs, bytes)) <
0) {
state = CUBEB_STATE_ERROR; state = CUBEB_STATE_ERROR;
break; break;
} }
@ -486,7 +489,8 @@ sun_io_routine(void * arg)
} }
if (to_read > 0) { if (to_read > 0) {
bytes = to_read * s->record.frame_size; bytes = to_read * s->record.frame_size;
if ((n = read(s->record.fd, (uint8_t *)s->record.buf + read_ofs, bytes)) < 0) { if ((n = read(s->record.fd, (uint8_t *)s->record.buf + read_ofs,
bytes)) < 0) {
state = CUBEB_STATE_ERROR; state = CUBEB_STATE_ERROR;
break; break;
} }
@ -505,20 +509,16 @@ sun_io_routine(void * arg)
} }
static int static int
sun_stream_init(cubeb * context, sun_stream_init(cubeb * context, cubeb_stream ** stream,
cubeb_stream ** stream, char const * stream_name, cubeb_devid input_device,
char const * stream_name,
cubeb_devid input_device,
cubeb_stream_params * input_stream_params, cubeb_stream_params * input_stream_params,
cubeb_devid output_device, cubeb_devid output_device,
cubeb_stream_params * output_stream_params, cubeb_stream_params * output_stream_params,
unsigned latency_frames, unsigned latency_frames, cubeb_data_callback data_callback,
cubeb_data_callback data_callback, cubeb_state_callback state_callback, void * user_ptr)
cubeb_state_callback state_callback,
void * user_ptr)
{ {
int ret = CUBEB_OK; int ret = CUBEB_OK;
cubeb_stream *s = NULL; cubeb_stream * s = NULL;
(void)stream_name; (void)stream_name;
(void)latency_frames; (void)latency_frames;
@ -529,14 +529,14 @@ sun_stream_init(cubeb * context,
s->record.fd = -1; s->record.fd = -1;
s->play.fd = -1; s->play.fd = -1;
if (input_device != 0) { if (input_device != 0) {
snprintf(s->record.name, sizeof(s->record.name), snprintf(s->record.name, sizeof(s->record.name), "/dev/audio%zu",
"/dev/audio%zu", (uintptr_t)input_device - 1); (uintptr_t)input_device - 1);
} else { } else {
snprintf(s->record.name, sizeof(s->record.name), "%s", SUN_DEFAULT_DEVICE); snprintf(s->record.name, sizeof(s->record.name), "%s", SUN_DEFAULT_DEVICE);
} }
if (output_device != 0) { if (output_device != 0) {
snprintf(s->play.name, sizeof(s->play.name), snprintf(s->play.name, sizeof(s->play.name), "/dev/audio%zu",
"/dev/audio%zu", (uintptr_t)output_device - 1); (uintptr_t)output_device - 1);
} else { } else {
snprintf(s->play.name, sizeof(s->play.name), "%s", SUN_DEFAULT_DEVICE); snprintf(s->play.name, sizeof(s->play.name), "%s", SUN_DEFAULT_DEVICE);
} }
@ -558,11 +558,13 @@ sun_stream_init(cubeb * context,
s->record.info.mode = AUMODE_RECORD; s->record.info.mode = AUMODE_RECORD;
#endif #endif
if ((ret = sun_copy_params(s->record.fd, s, input_stream_params, if ((ret = sun_copy_params(s->record.fd, s, input_stream_params,
&s->record.info, &s->record.info.record)) != CUBEB_OK) { &s->record.info, &s->record.info.record)) !=
CUBEB_OK) {
LOG("Setting record params failed"); LOG("Setting record params failed");
goto error; goto error;
} }
s->record.floating = (input_stream_params->format == CUBEB_SAMPLE_FLOAT32NE); s->record.floating =
(input_stream_params->format == CUBEB_SAMPLE_FLOAT32NE);
} }
if (output_stream_params != NULL) { if (output_stream_params != NULL) {
if (output_stream_params->prefs & CUBEB_STREAM_PREF_LOOPBACK) { if (output_stream_params->prefs & CUBEB_STREAM_PREF_LOOPBACK) {
@ -582,7 +584,8 @@ sun_stream_init(cubeb * context,
s->play.info.mode = AUMODE_PLAY; s->play.info.mode = AUMODE_PLAY;
#endif #endif
if ((ret = sun_copy_params(s->play.fd, s, output_stream_params, if ((ret = sun_copy_params(s->play.fd, s, output_stream_params,
&s->play.info, &s->play.info.play)) != CUBEB_OK) { &s->play.info, &s->play.info.play)) !=
CUBEB_OK) {
LOG("Setting play params failed"); LOG("Setting play params failed");
goto error; goto error;
} }
@ -597,17 +600,18 @@ sun_stream_init(cubeb * context,
LOG("Failed to create mutex"); LOG("Failed to create mutex");
goto error; goto error;
} }
s->play.frame_size = s->play.info.play.channels * s->play.frame_size =
(s->play.info.play.precision / 8); s->play.info.play.channels * (s->play.info.play.precision / 8);
if (s->play.fd != -1 && if (s->play.fd != -1 &&
(s->play.buf = calloc(SUN_BUFFER_FRAMES, s->play.frame_size)) == NULL) { (s->play.buf = calloc(SUN_BUFFER_FRAMES, s->play.frame_size)) == NULL) {
ret = CUBEB_ERROR; ret = CUBEB_ERROR;
goto error; goto error;
} }
s->record.frame_size = s->record.info.record.channels * s->record.frame_size =
(s->record.info.record.precision / 8); s->record.info.record.channels * (s->record.info.record.precision / 8);
if (s->record.fd != -1 && if (s->record.fd != -1 &&
(s->record.buf = calloc(SUN_BUFFER_FRAMES, s->record.frame_size)) == NULL) { (s->record.buf = calloc(SUN_BUFFER_FRAMES, s->record.frame_size)) ==
NULL) {
ret = CUBEB_ERROR; ret = CUBEB_ERROR;
goto error; goto error;
} }
@ -688,10 +692,10 @@ sun_get_current_device(cubeb_stream * stream, cubeb_device ** const device)
if (*device == NULL) { if (*device == NULL) {
return CUBEB_ERROR; return CUBEB_ERROR;
} }
(*device)->input_name = stream->record.fd != -1 ? (*device)->input_name =
strdup(stream->record.name) : NULL; stream->record.fd != -1 ? strdup(stream->record.name) : NULL;
(*device)->output_name = stream->play.fd != -1 ? (*device)->output_name =
strdup(stream->play.name) : NULL; stream->play.fd != -1 ? strdup(stream->play.name) : NULL;
return CUBEB_OK; return CUBEB_OK;
} }
@ -706,26 +710,24 @@ sun_stream_device_destroy(cubeb_stream * stream, cubeb_device * device)
} }
static struct cubeb_ops const sun_ops = { static struct cubeb_ops const sun_ops = {
.init = sun_init, .init = sun_init,
.get_backend_id = sun_get_backend_id, .get_backend_id = sun_get_backend_id,
.get_max_channel_count = sun_get_max_channel_count, .get_max_channel_count = sun_get_max_channel_count,
.get_min_latency = sun_get_min_latency, .get_min_latency = sun_get_min_latency,
.get_preferred_sample_rate = sun_get_preferred_sample_rate, .get_preferred_sample_rate = sun_get_preferred_sample_rate,
.enumerate_devices = sun_enumerate_devices, .enumerate_devices = sun_enumerate_devices,
.device_collection_destroy = sun_device_collection_destroy, .device_collection_destroy = sun_device_collection_destroy,
.destroy = sun_destroy, .destroy = sun_destroy,
.stream_init = sun_stream_init, .stream_init = sun_stream_init,
.stream_destroy = sun_stream_destroy, .stream_destroy = sun_stream_destroy,
.stream_start = sun_stream_start, .stream_start = sun_stream_start,
.stream_stop = sun_stream_stop, .stream_stop = sun_stream_stop,
.stream_reset_default_device = NULL, .stream_get_position = sun_stream_get_position,
.stream_get_position = sun_stream_get_position, .stream_get_latency = sun_stream_get_latency,
.stream_get_latency = sun_stream_get_latency, .stream_get_input_latency = NULL,
.stream_get_input_latency = NULL, .stream_set_volume = sun_stream_set_volume,
.stream_set_volume = sun_stream_set_volume, .stream_set_name = NULL,
.stream_set_name = NULL, .stream_get_current_device = sun_get_current_device,
.stream_get_current_device = sun_get_current_device, .stream_device_destroy = sun_stream_device_destroy,
.stream_device_destroy = sun_stream_device_destroy, .stream_register_device_changed_callback = NULL,
.stream_register_device_changed_callback = NULL, .register_device_collection_changed = NULL};
.register_device_collection_changed = NULL
};

View file

@ -7,18 +7,19 @@
#include "cubeb_utils.h" #include "cubeb_utils.h"
size_t cubeb_sample_size(cubeb_sample_format format) size_t
cubeb_sample_size(cubeb_sample_format format)
{ {
switch (format) { switch (format) {
case CUBEB_SAMPLE_S16LE: case CUBEB_SAMPLE_S16LE:
case CUBEB_SAMPLE_S16BE: case CUBEB_SAMPLE_S16BE:
return sizeof(int16_t); return sizeof(int16_t);
case CUBEB_SAMPLE_FLOAT32LE: case CUBEB_SAMPLE_FLOAT32LE:
case CUBEB_SAMPLE_FLOAT32BE: case CUBEB_SAMPLE_FLOAT32BE:
return sizeof(float); return sizeof(float);
default: default:
// should never happen as all cases are handled above. // should never happen as all cases are handled above.
assert(false); assert(false);
return 0; return 0;
} }
} }

View file

@ -12,10 +12,10 @@
#ifdef __cplusplus #ifdef __cplusplus
#include <stdint.h>
#include <string.h>
#include <assert.h> #include <assert.h>
#include <mutex> #include <mutex>
#include <stdint.h>
#include <string.h>
#include <type_traits> #include <type_traits>
#if defined(_WIN32) #if defined(_WIN32)
#include "cubeb_utils_win.h" #include "cubeb_utils_win.h"
@ -24,8 +24,9 @@
#endif #endif
/** Similar to memcpy, but accounts for the size of an element. */ /** Similar to memcpy, but accounts for the size of an element. */
template<typename T> template <typename T>
void PodCopy(T * destination, const T * source, size_t count) void
PodCopy(T * destination, const T * source, size_t count)
{ {
static_assert(std::is_trivial<T>::value, "Requires trivial type"); static_assert(std::is_trivial<T>::value, "Requires trivial type");
assert(destination && source); assert(destination && source);
@ -33,8 +34,9 @@ void PodCopy(T * destination, const T * source, size_t count)
} }
/** Similar to memmove, but accounts for the size of an element. */ /** Similar to memmove, but accounts for the size of an element. */
template<typename T> template <typename T>
void PodMove(T * destination, const T * source, size_t count) void
PodMove(T * destination, const T * source, size_t count)
{ {
static_assert(std::is_trivial<T>::value, "Requires trivial type"); static_assert(std::is_trivial<T>::value, "Requires trivial type");
assert(destination && source); assert(destination && source);
@ -42,133 +44,118 @@ void PodMove(T * destination, const T * source, size_t count)
} }
/** Similar to a memset to zero, but accounts for the size of an element. */ /** Similar to a memset to zero, but accounts for the size of an element. */
template<typename T> template <typename T>
void PodZero(T * destination, size_t count) void
PodZero(T * destination, size_t count)
{ {
static_assert(std::is_trivial<T>::value, "Requires trivial type"); static_assert(std::is_trivial<T>::value, "Requires trivial type");
assert(destination); assert(destination);
memset(destination, 0, count * sizeof(T)); memset(destination, 0, count * sizeof(T));
} }
namespace { namespace {
template<typename T, typename Trait> template <typename T, typename Trait>
void Copy(T * destination, const T * source, size_t count, Trait) void
Copy(T * destination, const T * source, size_t count, Trait)
{ {
for (size_t i = 0; i < count; i++) { for (size_t i = 0; i < count; i++) {
destination[i] = source[i]; destination[i] = source[i];
} }
} }
template<typename T> template <typename T>
void Copy(T * destination, const T * source, size_t count, std::true_type) void
Copy(T * destination, const T * source, size_t count, std::true_type)
{ {
PodCopy(destination, source, count); PodCopy(destination, source, count);
} }
} } // namespace
/** /**
* This allows copying a number of elements from a `source` pointer to a * This allows copying a number of elements from a `source` pointer to a
* `destination` pointer, using `memcpy` if it is safe to do so, or a loop that * `destination` pointer, using `memcpy` if it is safe to do so, or a loop that
* calls the constructors and destructors otherwise. * calls the constructors and destructors otherwise.
*/ */
template<typename T> template <typename T>
void Copy(T * destination, const T * source, size_t count) void
Copy(T * destination, const T * source, size_t count)
{ {
assert(destination && source); assert(destination && source);
Copy(destination, source, count, typename std::is_trivial<T>::type()); Copy(destination, source, count, typename std::is_trivial<T>::type());
} }
namespace { namespace {
template<typename T, typename Trait> template <typename T, typename Trait>
void ConstructDefault(T * destination, size_t count, Trait) void
ConstructDefault(T * destination, size_t count, Trait)
{ {
for (size_t i = 0; i < count; i++) { for (size_t i = 0; i < count; i++) {
destination[i] = T(); destination[i] = T();
} }
} }
template<typename T> template <typename T>
void ConstructDefault(T * destination, void
size_t count, std::true_type) ConstructDefault(T * destination, size_t count, std::true_type)
{ {
PodZero(destination, count); PodZero(destination, count);
} }
} } // namespace
/** /**
* This allows zeroing (using memset) or default-constructing a number of * This allows zeroing (using memset) or default-constructing a number of
* elements calling the constructors and destructors if necessary. * elements calling the constructors and destructors if necessary.
*/ */
template<typename T> template <typename T>
void ConstructDefault(T * destination, size_t count) void
ConstructDefault(T * destination, size_t count)
{ {
assert(destination); assert(destination);
ConstructDefault(destination, count, ConstructDefault(destination, count, typename std::is_arithmetic<T>::type());
typename std::is_arithmetic<T>::type());
} }
template<typename T> template <typename T> class auto_array {
class auto_array
{
public: public:
explicit auto_array(uint32_t capacity = 0) explicit auto_array(uint32_t capacity = 0)
: data_(capacity ? new T[capacity] : nullptr) : data_(capacity ? new T[capacity] : nullptr), capacity_(capacity),
, capacity_(capacity) length_(0)
, length_(0)
{}
~auto_array()
{ {
delete [] data_;
} }
~auto_array() { delete[] data_; }
/** Get a constant pointer to the underlying data. */ /** Get a constant pointer to the underlying data. */
T * data() const T * data() const { return data_; }
{
return data_;
}
T * end() const T * end() const { return data_ + length_; }
{
return data_ + length_;
}
const T& at(size_t index) const const T & at(size_t index) const
{ {
assert(index < length_ && "out of range"); assert(index < length_ && "out of range");
return data_[index]; return data_[index];
} }
T& at(size_t index) T & at(size_t index)
{ {
assert(index < length_ && "out of range"); assert(index < length_ && "out of range");
return data_[index]; return data_[index];
} }
/** Get how much underlying storage this auto_array has. */ /** Get how much underlying storage this auto_array has. */
size_t capacity() const size_t capacity() const { return capacity_; }
{
return capacity_;
}
/** Get how much elements this auto_array contains. */ /** Get how much elements this auto_array contains. */
size_t length() const size_t length() const { return length_; }
{
return length_;
}
/** Keeps the storage, but removes all the elements from the array. */ /** Keeps the storage, but removes all the elements from the array. */
void clear() void clear() { length_ = 0; }
{
length_ = 0;
}
/** Change the storage of this auto array, copying the elements to the new /** Change the storage of this auto array, copying the elements to the new
* storage. * storage.
* @returns true in case of success * @returns true in case of success
* @returns false if the new capacity is not big enough to accomodate for the * @returns false if the new capacity is not big enough to accomodate for the
* elements in the array. * elements in the array.
*/ */
bool reserve(size_t new_capacity) bool reserve(size_t new_capacity)
{ {
if (new_capacity < length_) { if (new_capacity < length_) {
@ -179,17 +166,17 @@ public:
PodCopy(new_data, data_, length_); PodCopy(new_data, data_, length_);
} }
capacity_ = new_capacity; capacity_ = new_capacity;
delete [] data_; delete[] data_;
data_ = new_data; data_ = new_data;
return true; return true;
} }
/** Append `length` elements to the end of the array, resizing the array if /** Append `length` elements to the end of the array, resizing the array if
* needed. * needed.
* @parameter elements the elements to append to the array. * @parameter elements the elements to append to the array.
* @parameter length the number of elements to append to the array. * @parameter length the number of elements to append to the array.
*/ */
void push(const T * elements, size_t length) void push(const T * elements, size_t length)
{ {
if (length_ + length > capacity_) { if (length_ + length > capacity_) {
@ -227,17 +214,14 @@ public:
} }
/** Return the number of free elements in the array. */ /** Return the number of free elements in the array. */
size_t available() const size_t available() const { return capacity_ - length_; }
{
return capacity_ - length_;
}
/** Copies `length` elements to `elements` if it is not null, and shift /** Copies `length` elements to `elements` if it is not null, and shift
* the remaining elements of the `auto_array` to the beginning. * the remaining elements of the `auto_array` to the beginning.
* @parameter elements a buffer to copy the elements to, or nullptr. * @parameter elements a buffer to copy the elements to, or nullptr.
* @parameter length the number of elements to copy. * @parameter length the number of elements to copy.
* @returns true in case of success. * @returns true in case of success.
* @returns false if the auto_array contains less than `length` elements. */ * @returns false if the auto_array contains less than `length` elements. */
bool pop(T * elements, size_t length) bool pop(T * elements, size_t length)
{ {
if (length > length_) { if (length > length_) {
@ -285,56 +269,38 @@ template <typename T>
struct auto_array_wrapper_impl : public auto_array_wrapper { struct auto_array_wrapper_impl : public auto_array_wrapper {
auto_array_wrapper_impl() {} auto_array_wrapper_impl() {}
explicit auto_array_wrapper_impl(uint32_t size) explicit auto_array_wrapper_impl(uint32_t size) : ar(size) {}
: ar(size)
{}
void push(void * elements, size_t length) override { void push(void * elements, size_t length) override
{
ar.push(static_cast<T *>(elements), length); ar.push(static_cast<T *>(elements), length);
} }
size_t length() override { size_t length() override { return ar.length(); }
return ar.length();
}
void push_silence(size_t length) override { void push_silence(size_t length) override { ar.push_silence(length); }
ar.push_silence(length);
}
bool pop(size_t length) override { bool pop(size_t length) override { return ar.pop(nullptr, length); }
return ar.pop(nullptr, length);
}
void * data() override { void * data() override { return ar.data(); }
return ar.data();
}
void * end() override { void * end() override { return ar.end(); }
return ar.end();
}
void clear() override { void clear() override { ar.clear(); }
ar.clear();
}
bool reserve(size_t capacity) override { bool reserve(size_t capacity) override { return ar.reserve(capacity); }
return ar.reserve(capacity);
}
void set_length(size_t length) override { void set_length(size_t length) override { ar.set_length(length); }
ar.set_length(length);
}
~auto_array_wrapper_impl() { ~auto_array_wrapper_impl() { ar.clear(); }
ar.clear();
}
private: private:
auto_array<T> ar; auto_array<T> ar;
}; };
extern "C" { extern "C" {
size_t cubeb_sample_size(cubeb_sample_format format); size_t
cubeb_sample_size(cubeb_sample_format format);
} }
using auto_lock = std::lock_guard<owned_critical_section>; using auto_lock = std::lock_guard<owned_critical_section>;

View file

@ -8,13 +8,12 @@
#if !defined(CUBEB_UTILS_UNIX) #if !defined(CUBEB_UTILS_UNIX)
#define CUBEB_UTILS_UNIX #define CUBEB_UTILS_UNIX
#include <pthread.h>
#include <errno.h> #include <errno.h>
#include <pthread.h>
#include <stdio.h> #include <stdio.h>
/* This wraps a critical section to track the owner in debug mode. */ /* This wraps a critical section to track the owner in debug mode. */
class owned_critical_section class owned_critical_section {
{
public: public:
owned_critical_section() owned_critical_section()
{ {
@ -29,7 +28,7 @@ public:
#ifndef NDEBUG #ifndef NDEBUG
int r = int r =
#endif #endif
pthread_mutex_init(&mutex, &attr); pthread_mutex_init(&mutex, &attr);
#ifndef NDEBUG #ifndef NDEBUG
assert(r == 0); assert(r == 0);
#endif #endif
@ -42,7 +41,7 @@ public:
#ifndef NDEBUG #ifndef NDEBUG
int r = int r =
#endif #endif
pthread_mutex_destroy(&mutex); pthread_mutex_destroy(&mutex);
#ifndef NDEBUG #ifndef NDEBUG
assert(r == 0); assert(r == 0);
#endif #endif
@ -53,7 +52,7 @@ public:
#ifndef NDEBUG #ifndef NDEBUG
int r = int r =
#endif #endif
pthread_mutex_lock(&mutex); pthread_mutex_lock(&mutex);
#ifndef NDEBUG #ifndef NDEBUG
assert(r == 0 && "Deadlock"); assert(r == 0 && "Deadlock");
#endif #endif
@ -64,7 +63,7 @@ public:
#ifndef NDEBUG #ifndef NDEBUG
int r = int r =
#endif #endif
pthread_mutex_unlock(&mutex); pthread_mutex_unlock(&mutex);
#ifndef NDEBUG #ifndef NDEBUG
assert(r == 0 && "Unlocking unlocked mutex"); assert(r == 0 && "Unlocking unlocked mutex");
#endif #endif
@ -82,8 +81,8 @@ private:
pthread_mutex_t mutex; pthread_mutex_t mutex;
// Disallow copy and assignment because pthread_mutex_t cannot be copied. // Disallow copy and assignment because pthread_mutex_t cannot be copied.
owned_critical_section(const owned_critical_section&); owned_critical_section(const owned_critical_section &);
owned_critical_section& operator=(const owned_critical_section&); owned_critical_section & operator=(const owned_critical_section &);
}; };
#endif /* CUBEB_UTILS_UNIX */ #endif /* CUBEB_UTILS_UNIX */

View file

@ -8,26 +8,23 @@
#if !defined(CUBEB_UTILS_WIN) #if !defined(CUBEB_UTILS_WIN)
#define CUBEB_UTILS_WIN #define CUBEB_UTILS_WIN
#include <windows.h>
#include "cubeb-internal.h" #include "cubeb-internal.h"
#include <windows.h>
/* This wraps a critical section to track the owner in debug mode, adapted from /* This wraps a critical section to track the owner in debug mode, adapted from
NSPR and http://blogs.msdn.com/b/oldnewthing/archive/2013/07/12/10433554.aspx */ NSPR and http://blogs.msdn.com/b/oldnewthing/archive/2013/07/12/10433554.aspx
class owned_critical_section */
{ class owned_critical_section {
public: public:
owned_critical_section() owned_critical_section()
#ifndef NDEBUG #ifndef NDEBUG
: owner(0) : owner(0)
#endif #endif
{ {
InitializeCriticalSection(&critical_section); InitializeCriticalSection(&critical_section);
} }
~owned_critical_section() ~owned_critical_section() { DeleteCriticalSection(&critical_section); }
{
DeleteCriticalSection(&critical_section);
}
void lock() void lock()
{ {
@ -64,8 +61,8 @@ private:
#endif #endif
// Disallow copy and assignment because CRICICAL_SECTION cannot be copied. // Disallow copy and assignment because CRICICAL_SECTION cannot be copied.
owned_critical_section(const owned_critical_section&); owned_critical_section(const owned_critical_section &);
owned_critical_section& operator=(const owned_critical_section&); owned_critical_section & operator=(const owned_critical_section &);
}; };
#endif /* CUBEB_UTILS_WIN */ #endif /* CUBEB_UTILS_WIN */

File diff suppressed because it is too large Load diff

View file

@ -8,70 +8,81 @@
#define WINVER 0x0501 #define WINVER 0x0501
#undef WIN32_LEAN_AND_MEAN #undef WIN32_LEAN_AND_MEAN
#include "cubeb-internal.h"
#include "cubeb/cubeb.h"
#include <malloc.h> #include <malloc.h>
#include <windows.h> #include <math.h>
#include <mmreg.h>
#include <mmsystem.h>
#include <process.h> #include <process.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <math.h> #include <windows.h>
#include "cubeb/cubeb.h"
#include "cubeb-internal.h" /* clang-format off */
/* These need to be included after windows.h */
#include <mmreg.h>
#include <mmsystem.h>
/* clang-format on */
/* This is missing from the MinGW headers. Use a safe fallback. */ /* This is missing from the MinGW headers. Use a safe fallback. */
#if !defined(MEMORY_ALLOCATION_ALIGNMENT) #if !defined(MEMORY_ALLOCATION_ALIGNMENT)
#define MEMORY_ALLOCATION_ALIGNMENT 16 #define MEMORY_ALLOCATION_ALIGNMENT 16
#endif #endif
/**This is also missing from the MinGW headers. It also appears to be undocumented by Microsoft.*/ /**This is also missing from the MinGW headers. It also appears to be
* undocumented by Microsoft.*/
#ifndef WAVE_FORMAT_48M08 #ifndef WAVE_FORMAT_48M08
#define WAVE_FORMAT_48M08 0x00001000 /* 48 kHz, Mono, 8-bit */ #define WAVE_FORMAT_48M08 0x00001000 /* 48 kHz, Mono, 8-bit */
#endif #endif
#ifndef WAVE_FORMAT_48M16 #ifndef WAVE_FORMAT_48M16
#define WAVE_FORMAT_48M16 0x00002000 /* 48 kHz, Mono, 16-bit */ #define WAVE_FORMAT_48M16 0x00002000 /* 48 kHz, Mono, 16-bit */
#endif #endif
#ifndef WAVE_FORMAT_48S08 #ifndef WAVE_FORMAT_48S08
#define WAVE_FORMAT_48S08 0x00004000 /* 48 kHz, Stereo, 8-bit */ #define WAVE_FORMAT_48S08 0x00004000 /* 48 kHz, Stereo, 8-bit */
#endif #endif
#ifndef WAVE_FORMAT_48S16 #ifndef WAVE_FORMAT_48S16
#define WAVE_FORMAT_48S16 0x00008000 /* 48 kHz, Stereo, 16-bit */ #define WAVE_FORMAT_48S16 0x00008000 /* 48 kHz, Stereo, 16-bit */
#endif #endif
#ifndef WAVE_FORMAT_96M08 #ifndef WAVE_FORMAT_96M08
#define WAVE_FORMAT_96M08 0x00010000 /* 96 kHz, Mono, 8-bit */ #define WAVE_FORMAT_96M08 0x00010000 /* 96 kHz, Mono, 8-bit */
#endif #endif
#ifndef WAVE_FORMAT_96M16 #ifndef WAVE_FORMAT_96M16
#define WAVE_FORMAT_96M16 0x00020000 /* 96 kHz, Mono, 16-bit */ #define WAVE_FORMAT_96M16 0x00020000 /* 96 kHz, Mono, 16-bit */
#endif #endif
#ifndef WAVE_FORMAT_96S08 #ifndef WAVE_FORMAT_96S08
#define WAVE_FORMAT_96S08 0x00040000 /* 96 kHz, Stereo, 8-bit */ #define WAVE_FORMAT_96S08 0x00040000 /* 96 kHz, Stereo, 8-bit */
#endif #endif
#ifndef WAVE_FORMAT_96S16 #ifndef WAVE_FORMAT_96S16
#define WAVE_FORMAT_96S16 0x00080000 /* 96 kHz, Stereo, 16-bit */ #define WAVE_FORMAT_96S16 0x00080000 /* 96 kHz, Stereo, 16-bit */
#endif #endif
/**Taken from winbase.h, also not in MinGW.*/ /**Taken from winbase.h, also not in MinGW.*/
#ifndef STACK_SIZE_PARAM_IS_A_RESERVATION #ifndef STACK_SIZE_PARAM_IS_A_RESERVATION
#define STACK_SIZE_PARAM_IS_A_RESERVATION 0x00010000 // Threads only #define STACK_SIZE_PARAM_IS_A_RESERVATION 0x00010000 // Threads only
#endif #endif
#ifndef DRVM_MAPPER #ifndef DRVM_MAPPER
#define DRVM_MAPPER (0x2000) #define DRVM_MAPPER (0x2000)
#endif #endif
#ifndef DRVM_MAPPER_PREFERRED_GET #ifndef DRVM_MAPPER_PREFERRED_GET
#define DRVM_MAPPER_PREFERRED_GET (DRVM_MAPPER+21) #define DRVM_MAPPER_PREFERRED_GET (DRVM_MAPPER + 21)
#endif #endif
#ifndef DRVM_MAPPER_CONSOLEVOICECOM_GET #ifndef DRVM_MAPPER_CONSOLEVOICECOM_GET
#define DRVM_MAPPER_CONSOLEVOICECOM_GET (DRVM_MAPPER+23) #define DRVM_MAPPER_CONSOLEVOICECOM_GET (DRVM_MAPPER + 23)
#endif #endif
#define CUBEB_STREAM_MAX 32 #define CUBEB_STREAM_MAX 32
#define NBUFS 4 #define NBUFS 4
const GUID KSDATAFORMAT_SUBTYPE_PCM = const GUID KSDATAFORMAT_SUBTYPE_PCM = {
{ 0x00000001, 0x0000, 0x0010, { 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 } }; 0x00000001,
const GUID KSDATAFORMAT_SUBTYPE_IEEE_FLOAT = 0x0000,
{ 0x00000003, 0x0000, 0x0010, { 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 } }; 0x0010,
{0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71}};
const GUID KSDATAFORMAT_SUBTYPE_IEEE_FLOAT = {
0x00000003,
0x0000,
0x0010,
{0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71}};
struct cubeb_stream_item { struct cubeb_stream_item {
SLIST_ENTRY head; SLIST_ENTRY head;
@ -110,6 +121,10 @@ struct cubeb_stream {
CRITICAL_SECTION lock; CRITICAL_SECTION lock;
uint64_t written; uint64_t written;
float soft_volume; float soft_volume;
/* For position wrap-around handling: */
size_t frame_size;
DWORD prev_pos_lo_dword;
DWORD pos_hi_dword;
}; };
static size_t static size_t
@ -175,7 +190,7 @@ winmm_refill_stream(cubeb_stream * stm)
hdr = winmm_get_next_buffer(stm); hdr = winmm_get_next_buffer(stm);
wanted = (DWORD) stm->buffer_size / bytes_per_frame(stm->params); wanted = (DWORD)stm->buffer_size / bytes_per_frame(stm->params);
/* It is assumed that the caller is holding this lock. It must be dropped /* It is assumed that the caller is holding this lock. It must be dropped
during the callback to avoid deadlocks. */ during the callback to avoid deadlocks. */
@ -199,16 +214,16 @@ winmm_refill_stream(cubeb_stream * stm)
if (stm->soft_volume != -1.0) { if (stm->soft_volume != -1.0) {
if (stm->params.format == CUBEB_SAMPLE_FLOAT32NE) { if (stm->params.format == CUBEB_SAMPLE_FLOAT32NE) {
float * b = (float *) hdr->lpData; float * b = (float *)hdr->lpData;
uint32_t i; uint32_t i;
for (i = 0; i < got * stm->params.channels; i++) { for (i = 0; i < got * stm->params.channels; i++) {
b[i] *= stm->soft_volume; b[i] *= stm->soft_volume;
} }
} else { } else {
short * b = (short *) hdr->lpData; short * b = (short *)hdr->lpData;
uint32_t i; uint32_t i;
for (i = 0; i < got * stm->params.channels; i++) { for (i = 0; i < got * stm->params.channels; i++) {
b[i] = (short) (b[i] * stm->soft_volume); b[i] = (short)(b[i] * stm->soft_volume);
} }
} }
} }
@ -223,10 +238,9 @@ winmm_refill_stream(cubeb_stream * stm)
LeaveCriticalSection(&stm->lock); LeaveCriticalSection(&stm->lock);
} }
static unsigned __stdcall static unsigned __stdcall winmm_buffer_thread(void * user_ptr)
winmm_buffer_thread(void * user_ptr)
{ {
cubeb * ctx = (cubeb *) user_ptr; cubeb * ctx = (cubeb *)user_ptr;
XASSERT(ctx); XASSERT(ctx);
for (;;) { for (;;) {
@ -242,7 +256,7 @@ winmm_buffer_thread(void * user_ptr)
item = InterlockedFlushSList(ctx->work); item = InterlockedFlushSList(ctx->work);
while (item != NULL) { while (item != NULL) {
PSLIST_ENTRY tmp = item; PSLIST_ENTRY tmp = item;
winmm_refill_stream(((struct cubeb_stream_item *) tmp)->stream); winmm_refill_stream(((struct cubeb_stream_item *)tmp)->stream);
item = item->Next; item = item->Next;
_aligned_free(tmp); _aligned_free(tmp);
} }
@ -256,16 +270,18 @@ winmm_buffer_thread(void * user_ptr)
} }
static void CALLBACK static void CALLBACK
winmm_buffer_callback(HWAVEOUT waveout, UINT msg, DWORD_PTR user_ptr, DWORD_PTR p1, DWORD_PTR p2) winmm_buffer_callback(HWAVEOUT waveout, UINT msg, DWORD_PTR user_ptr,
DWORD_PTR p1, DWORD_PTR p2)
{ {
cubeb_stream * stm = (cubeb_stream *) user_ptr; cubeb_stream * stm = (cubeb_stream *)user_ptr;
struct cubeb_stream_item * item; struct cubeb_stream_item * item;
if (msg != WOM_DONE) { if (msg != WOM_DONE) {
return; return;
} }
item = _aligned_malloc(sizeof(struct cubeb_stream_item), MEMORY_ALLOCATION_ALIGNMENT); item = _aligned_malloc(sizeof(struct cubeb_stream_item),
MEMORY_ALLOCATION_ALIGNMENT);
XASSERT(item); XASSERT(item);
item->stream = stm; item->stream = stm;
InterlockedPushEntrySList(stm->context->work, &item->head); InterlockedPushEntrySList(stm->context->work, &item->head);
@ -284,7 +300,8 @@ calculate_minimum_latency(void)
return 500; return 500;
} }
/* Vista's WinMM implementation underruns when less than 200ms of audio is buffered. */ /* Vista's WinMM implementation underruns when less than 200ms of audio is
* buffered. */
memset(&osvi, 0, sizeof(OSVERSIONINFOEX)); memset(&osvi, 0, sizeof(OSVERSIONINFOEX));
osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX); osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);
osvi.dwMajorVersion = 6; osvi.dwMajorVersion = 6;
@ -294,14 +311,16 @@ calculate_minimum_latency(void)
VER_SET_CONDITION(mask, VER_MAJORVERSION, VER_EQUAL); VER_SET_CONDITION(mask, VER_MAJORVERSION, VER_EQUAL);
VER_SET_CONDITION(mask, VER_MINORVERSION, VER_EQUAL); VER_SET_CONDITION(mask, VER_MINORVERSION, VER_EQUAL);
if (VerifyVersionInfo(&osvi, VER_MAJORVERSION | VER_MINORVERSION, mask) != 0) { if (VerifyVersionInfo(&osvi, VER_MAJORVERSION | VER_MINORVERSION, mask) !=
0) {
return 200; return 200;
} }
return 100; return 100;
} }
static void winmm_destroy(cubeb * ctx); static void
winmm_destroy(cubeb * ctx);
/*static*/ int /*static*/ int
winmm_init(cubeb ** context, char const * context_name) winmm_init(cubeb ** context, char const * context_name)
@ -331,7 +350,9 @@ winmm_init(cubeb ** context, char const * context_name)
return CUBEB_ERROR; return CUBEB_ERROR;
} }
ctx->thread = (HANDLE) _beginthreadex(NULL, 256 * 1024, winmm_buffer_thread, ctx, STACK_SIZE_PARAM_IS_A_RESERVATION, NULL); ctx->thread =
(HANDLE)_beginthreadex(NULL, 256 * 1024, winmm_buffer_thread, ctx,
STACK_SIZE_PARAM_IS_A_RESERVATION, NULL);
if (!ctx->thread) { if (!ctx->thread) {
winmm_destroy(ctx); winmm_destroy(ctx);
return CUBEB_ERROR; return CUBEB_ERROR;
@ -382,18 +403,18 @@ winmm_destroy(cubeb * ctx)
free(ctx); free(ctx);
} }
static void winmm_stream_destroy(cubeb_stream * stm); static void
winmm_stream_destroy(cubeb_stream * stm);
static int static int
winmm_stream_init(cubeb * context, cubeb_stream ** stream, char const * stream_name, winmm_stream_init(cubeb * context, cubeb_stream ** stream,
cubeb_devid input_device, char const * stream_name, cubeb_devid input_device,
cubeb_stream_params * input_stream_params, cubeb_stream_params * input_stream_params,
cubeb_devid output_device, cubeb_devid output_device,
cubeb_stream_params * output_stream_params, cubeb_stream_params * output_stream_params,
unsigned int latency_frames, unsigned int latency_frames,
cubeb_data_callback data_callback, cubeb_data_callback data_callback,
cubeb_state_callback state_callback, cubeb_state_callback state_callback, void * user_ptr)
void * user_ptr)
{ {
MMRESULT r; MMRESULT r;
WAVEFORMATEXTENSIBLE wfx; WAVEFORMATEXTENSIBLE wfx;
@ -452,8 +473,10 @@ winmm_stream_init(cubeb * context, cubeb_stream ** stream, char const * stream_n
return CUBEB_ERROR_INVALID_FORMAT; return CUBEB_ERROR_INVALID_FORMAT;
} }
wfx.Format.nBlockAlign = (wfx.Format.wBitsPerSample * wfx.Format.nChannels) / 8; wfx.Format.nBlockAlign =
wfx.Format.nAvgBytesPerSec = wfx.Format.nSamplesPerSec * wfx.Format.nBlockAlign; (wfx.Format.wBitsPerSample * wfx.Format.nChannels) / 8;
wfx.Format.nAvgBytesPerSec =
wfx.Format.nSamplesPerSec * wfx.Format.nBlockAlign;
wfx.Samples.wValidBitsPerSample = wfx.Format.wBitsPerSample; wfx.Samples.wValidBitsPerSample = wfx.Format.wBitsPerSample;
EnterCriticalSection(&context->lock); EnterCriticalSection(&context->lock);
@ -485,9 +508,11 @@ winmm_stream_init(cubeb * context, cubeb_stream ** stream, char const * stream_n
latency_ms = context->minimum_latency_ms; latency_ms = context->minimum_latency_ms;
} }
bufsz = (size_t) (stm->params.rate / 1000.0 * latency_ms * bytes_per_frame(stm->params) / NBUFS); bufsz = (size_t)(stm->params.rate / 1000.0 * latency_ms *
bytes_per_frame(stm->params) / NBUFS);
if (bufsz % bytes_per_frame(stm->params) != 0) { if (bufsz % bytes_per_frame(stm->params) != 0) {
bufsz += bytes_per_frame(stm->params) - (bufsz % bytes_per_frame(stm->params)); bufsz +=
bytes_per_frame(stm->params) - (bufsz % bytes_per_frame(stm->params));
} }
XASSERT(bufsz % bytes_per_frame(stm->params) == 0); XASSERT(bufsz % bytes_per_frame(stm->params) == 0);
@ -506,7 +531,7 @@ winmm_stream_init(cubeb * context, cubeb_stream ** stream, char const * stream_n
/* winmm_buffer_callback will be called during waveOutOpen, so all /* winmm_buffer_callback will be called during waveOutOpen, so all
other initialization must be complete before calling it. */ other initialization must be complete before calling it. */
r = waveOutOpen(&stm->waveout, WAVE_MAPPER, &wfx.Format, r = waveOutOpen(&stm->waveout, WAVE_MAPPER, &wfx.Format,
(DWORD_PTR) winmm_buffer_callback, (DWORD_PTR) stm, (DWORD_PTR)winmm_buffer_callback, (DWORD_PTR)stm,
CALLBACK_FUNCTION); CALLBACK_FUNCTION);
if (r != MMSYSERR_NOERROR) { if (r != MMSYSERR_NOERROR) {
winmm_stream_destroy(stm); winmm_stream_destroy(stm);
@ -536,6 +561,10 @@ winmm_stream_init(cubeb * context, cubeb_stream ** stream, char const * stream_n
winmm_refill_stream(stm); winmm_refill_stream(stm);
} }
stm->frame_size = bytes_per_frame(stm->params);
stm->prev_pos_lo_dword = 0;
stm->pos_hi_dword = 0;
*stream = stm; *stream = stm;
return CUBEB_OK; return CUBEB_OK;
@ -580,7 +609,8 @@ winmm_stream_destroy(cubeb_stream * stm)
for (i = 0; i < NBUFS; ++i) { for (i = 0; i < NBUFS; ++i) {
if (stm->buffers[i].dwFlags & WHDR_PREPARED) { if (stm->buffers[i].dwFlags & WHDR_PREPARED) {
waveOutUnprepareHeader(stm->waveout, &stm->buffers[i], sizeof(stm->buffers[i])); waveOutUnprepareHeader(stm->waveout, &stm->buffers[i],
sizeof(stm->buffers[i]));
} }
} }
@ -619,7 +649,8 @@ winmm_get_max_channel_count(cubeb * ctx, uint32_t * max_channels)
} }
static int static int
winmm_get_min_latency(cubeb * ctx, cubeb_stream_params params, uint32_t * latency) winmm_get_min_latency(cubeb * ctx, cubeb_stream_params params,
uint32_t * latency)
{ {
// 100ms minimum, if we are not in a bizarre configuration. // 100ms minimum, if we are not in a bizarre configuration.
*latency = ctx->minimum_latency_ms * params.rate / 1000; *latency = ctx->minimum_latency_ms * params.rate / 1000;
@ -686,6 +717,58 @@ winmm_stream_stop(cubeb_stream * stm)
return CUBEB_OK; return CUBEB_OK;
} }
/*
Microsoft wave audio docs say "samples are the preferred time format in which
to represent the current position", but relying on this causes problems on
Windows XP, the only OS cubeb_winmm is used on.
While the wdmaud.sys driver internally tracks a 64-bit position and ensures no
backward movement, the WinMM API limits the position returned from
waveOutGetPosition() to a 32-bit DWORD (this applies equally to XP x64). The
higher 32 bits are chopped off, and to an API consumer the position can appear
to move backward.
In theory, even a 32-bit TIME_SAMPLES position should provide plenty of
playback time for typical use cases before this pseudo wrap-around, e.g:
(2^32 - 1)/48000 = ~24:51:18 for 48.0 kHz stereo;
(2^32 - 1)/44100 = ~27:03:12 for 44.1 kHz stereo.
In reality, wdmaud.sys doesn't provide a TIME_SAMPLES position at all, only a
32-bit TIME_BYTES position, from which wdmaud.drv derives TIME_SAMPLES:
SamplePos = (BytePos * 8) / BitsPerFrame,
where BitsPerFrame = Channels * BitsPerSample,
Per dom\media\AudioSampleFormat.h, desktop builds always use 32-bit FLOAT32
samples, so the maximum for TIME_SAMPLES should be:
(2^29 - 1)/48000 = ~03:06:25;
(2^29 - 1)/44100 = ~03:22:54.
This might still be OK for typical browser usage, but there's also a bug in the
formula above: BytePos * 8 (BytePos << 3) is done on a 32-bit BytePos, without
first casting it to 64 bits, so the highest 3 bits, if set, would get shifted
out, and the maximum possible TIME_SAMPLES drops unacceptably low:
(2^26 - 1)/48000 = ~00:23:18;
(2^26 - 1)/44100 = ~00:25:22.
To work around these limitations, we just get the position in TIME_BYTES,
recover the 64-bit value, and do our own conversion to samples.
*/
/* Convert chopped 32-bit waveOutGetPosition() into 64-bit true position. */
static uint64_t
update_64bit_position(cubeb_stream * stm, DWORD pos_lo_dword)
{
/* Caller should be holding stm->lock. */
if (pos_lo_dword < stm->prev_pos_lo_dword) {
stm->pos_hi_dword++;
LOG("waveOutGetPosition() has wrapped around: %#lx -> %#lx",
stm->prev_pos_lo_dword, pos_lo_dword);
LOG("Wrap-around count = %#lx", stm->pos_hi_dword);
LOG("Current 64-bit position = %#llx",
(((uint64_t)stm->pos_hi_dword) << 32) | ((uint64_t)pos_lo_dword));
}
stm->prev_pos_lo_dword = pos_lo_dword;
return (((uint64_t)stm->pos_hi_dword) << 32) | ((uint64_t)pos_lo_dword);
}
static int static int
winmm_stream_get_position(cubeb_stream * stm, uint64_t * position) winmm_stream_get_position(cubeb_stream * stm, uint64_t * position)
{ {
@ -693,15 +776,17 @@ winmm_stream_get_position(cubeb_stream * stm, uint64_t * position)
MMTIME time; MMTIME time;
EnterCriticalSection(&stm->lock); EnterCriticalSection(&stm->lock);
time.wType = TIME_SAMPLES; /* See the long comment above for why not just use TIME_SAMPLES here. */
time.wType = TIME_BYTES;
r = waveOutGetPosition(stm->waveout, &time, sizeof(time)); r = waveOutGetPosition(stm->waveout, &time, sizeof(time));
LeaveCriticalSection(&stm->lock);
if (r != MMSYSERR_NOERROR || time.wType != TIME_SAMPLES) { if (r != MMSYSERR_NOERROR || time.wType != TIME_BYTES) {
LeaveCriticalSection(&stm->lock);
return CUBEB_ERROR; return CUBEB_ERROR;
} }
*position = time.u.sample; *position = update_64bit_position(stm, time.u.cb) / stm->frame_size;
LeaveCriticalSection(&stm->lock);
return CUBEB_OK; return CUBEB_OK;
} }
@ -711,20 +796,24 @@ winmm_stream_get_latency(cubeb_stream * stm, uint32_t * latency)
{ {
MMRESULT r; MMRESULT r;
MMTIME time; MMTIME time;
uint64_t written; uint64_t written, position;
EnterCriticalSection(&stm->lock); EnterCriticalSection(&stm->lock);
time.wType = TIME_SAMPLES; /* See the long comment above for why not just use TIME_SAMPLES here. */
time.wType = TIME_BYTES;
r = waveOutGetPosition(stm->waveout, &time, sizeof(time)); r = waveOutGetPosition(stm->waveout, &time, sizeof(time));
written = stm->written;
LeaveCriticalSection(&stm->lock);
if (r != MMSYSERR_NOERROR || time.wType != TIME_SAMPLES) { if (r != MMSYSERR_NOERROR || time.wType != TIME_BYTES) {
LeaveCriticalSection(&stm->lock);
return CUBEB_ERROR; return CUBEB_ERROR;
} }
XASSERT(written - time.u.sample <= UINT32_MAX); position = update_64bit_position(stm, time.u.cb);
*latency = (uint32_t) (written - time.u.sample); written = stm->written;
LeaveCriticalSection(&stm->lock);
XASSERT((written - (position / stm->frame_size)) <= UINT32_MAX);
*latency = (uint32_t)(written - (position / stm->frame_size));
return CUBEB_OK; return CUBEB_OK;
} }
@ -738,11 +827,18 @@ winmm_stream_set_volume(cubeb_stream * stm, float volume)
return CUBEB_OK; return CUBEB_OK;
} }
#define MM_11025HZ_MASK (WAVE_FORMAT_1M08 | WAVE_FORMAT_1M16 | WAVE_FORMAT_1S08 | WAVE_FORMAT_1S16) #define MM_11025HZ_MASK \
#define MM_22050HZ_MASK (WAVE_FORMAT_2M08 | WAVE_FORMAT_2M16 | WAVE_FORMAT_2S08 | WAVE_FORMAT_2S16) (WAVE_FORMAT_1M08 | WAVE_FORMAT_1M16 | WAVE_FORMAT_1S08 | WAVE_FORMAT_1S16)
#define MM_44100HZ_MASK (WAVE_FORMAT_4M08 | WAVE_FORMAT_4M16 | WAVE_FORMAT_4S08 | WAVE_FORMAT_4S16) #define MM_22050HZ_MASK \
#define MM_48000HZ_MASK (WAVE_FORMAT_48M08 | WAVE_FORMAT_48M16 | WAVE_FORMAT_48S08 | WAVE_FORMAT_48S16) (WAVE_FORMAT_2M08 | WAVE_FORMAT_2M16 | WAVE_FORMAT_2S08 | WAVE_FORMAT_2S16)
#define MM_96000HZ_MASK (WAVE_FORMAT_96M08 | WAVE_FORMAT_96M16 | WAVE_FORMAT_96S08 | WAVE_FORMAT_96S16) #define MM_44100HZ_MASK \
(WAVE_FORMAT_4M08 | WAVE_FORMAT_4M16 | WAVE_FORMAT_4S08 | WAVE_FORMAT_4S16)
#define MM_48000HZ_MASK \
(WAVE_FORMAT_48M08 | WAVE_FORMAT_48M16 | WAVE_FORMAT_48S08 | \
WAVE_FORMAT_48S16)
#define MM_96000HZ_MASK \
(WAVE_FORMAT_96M08 | WAVE_FORMAT_96M16 | WAVE_FORMAT_96S08 | \
WAVE_FORMAT_96S16)
static void static void
winmm_calculate_device_rate(cubeb_device_info * info, DWORD formats) winmm_calculate_device_rate(cubeb_device_info * info, DWORD formats)
{ {
@ -752,17 +848,20 @@ winmm_calculate_device_rate(cubeb_device_info * info, DWORD formats)
info->max_rate = 11025; info->max_rate = 11025;
} }
if (formats & MM_22050HZ_MASK) { if (formats & MM_22050HZ_MASK) {
if (info->min_rate == 0) info->min_rate = 22050; if (info->min_rate == 0)
info->min_rate = 22050;
info->max_rate = 22050; info->max_rate = 22050;
info->default_rate = 22050; info->default_rate = 22050;
} }
if (formats & MM_44100HZ_MASK) { if (formats & MM_44100HZ_MASK) {
if (info->min_rate == 0) info->min_rate = 44100; if (info->min_rate == 0)
info->min_rate = 44100;
info->max_rate = 44100; info->max_rate = 44100;
info->default_rate = 44100; info->default_rate = 44100;
} }
if (formats & MM_48000HZ_MASK) { if (formats & MM_48000HZ_MASK) {
if (info->min_rate == 0) info->min_rate = 48000; if (info->min_rate == 0)
info->min_rate = 48000;
info->max_rate = 48000; info->max_rate = 48000;
info->default_rate = 48000; info->default_rate = 48000;
} }
@ -775,11 +874,14 @@ winmm_calculate_device_rate(cubeb_device_info * info, DWORD formats)
} }
} }
#define MM_S16_MASK (WAVE_FORMAT_1M16 | WAVE_FORMAT_1S16 | WAVE_FORMAT_2M16 | WAVE_FORMAT_2S16 | WAVE_FORMAT_4M16 | \ #define MM_S16_MASK \
WAVE_FORMAT_4S16 | WAVE_FORMAT_48M16 | WAVE_FORMAT_48S16 | WAVE_FORMAT_96M16 | WAVE_FORMAT_96S16) (WAVE_FORMAT_1M16 | WAVE_FORMAT_1S16 | WAVE_FORMAT_2M16 | WAVE_FORMAT_2S16 | \
WAVE_FORMAT_4M16 | WAVE_FORMAT_4S16 | WAVE_FORMAT_48M16 | \
WAVE_FORMAT_48S16 | WAVE_FORMAT_96M16 | WAVE_FORMAT_96S16)
static int static int
winmm_query_supported_formats(UINT devid, DWORD formats, winmm_query_supported_formats(UINT devid, DWORD formats,
cubeb_device_fmt * supfmt, cubeb_device_fmt * deffmt) cubeb_device_fmt * supfmt,
cubeb_device_fmt * deffmt)
{ {
WAVEFORMATEXTENSIBLE wfx; WAVEFORMATEXTENSIBLE wfx;
@ -793,13 +895,16 @@ winmm_query_supported_formats(UINT devid, DWORD formats,
wfx.Format.nChannels = 2; wfx.Format.nChannels = 2;
wfx.Format.nSamplesPerSec = 44100; wfx.Format.nSamplesPerSec = 44100;
wfx.Format.wBitsPerSample = 32; wfx.Format.wBitsPerSample = 32;
wfx.Format.nBlockAlign = (wfx.Format.wBitsPerSample * wfx.Format.nChannels) / 8; wfx.Format.nBlockAlign =
wfx.Format.nAvgBytesPerSec = wfx.Format.nSamplesPerSec * wfx.Format.nBlockAlign; (wfx.Format.wBitsPerSample * wfx.Format.nChannels) / 8;
wfx.Format.nAvgBytesPerSec =
wfx.Format.nSamplesPerSec * wfx.Format.nBlockAlign;
wfx.Format.cbSize = 22; wfx.Format.cbSize = 22;
wfx.Samples.wValidBitsPerSample = wfx.Format.wBitsPerSample; wfx.Samples.wValidBitsPerSample = wfx.Format.wBitsPerSample;
wfx.dwChannelMask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT; wfx.dwChannelMask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT;
wfx.SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT; wfx.SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
if (waveOutOpen(NULL, devid, &wfx.Format, 0, 0, WAVE_FORMAT_QUERY) == MMSYSERR_NOERROR) if (waveOutOpen(NULL, devid, &wfx.Format, 0, 0, WAVE_FORMAT_QUERY) ==
MMSYSERR_NOERROR)
*supfmt = (cubeb_device_fmt)(*supfmt | CUBEB_DEVICE_FMT_F32LE); *supfmt = (cubeb_device_fmt)(*supfmt | CUBEB_DEVICE_FMT_F32LE);
return (*deffmt != 0) ? CUBEB_OK : CUBEB_ERROR; return (*deffmt != 0) ? CUBEB_OK : CUBEB_ERROR;
@ -813,10 +918,10 @@ guid_to_cstr(LPGUID guid)
return NULL; return NULL;
} }
_snprintf_s(ret, 40, _TRUNCATE, _snprintf_s(ret, 40, _TRUNCATE,
"{%08X-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X}", "{%08X-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X}", guid->Data1,
guid->Data1, guid->Data2, guid->Data3, guid->Data2, guid->Data3, guid->Data4[0], guid->Data4[1],
guid->Data4[0], guid->Data4[1], guid->Data4[2], guid->Data4[3], guid->Data4[2], guid->Data4[3], guid->Data4[4], guid->Data4[5],
guid->Data4[4], guid->Data4[5], guid->Data4[6], guid->Data4[7]); guid->Data4[6], guid->Data4[7]);
return ret; return ret;
} }
@ -826,13 +931,15 @@ winmm_query_preferred_out_device(UINT devid)
DWORD mmpref = WAVE_MAPPER, compref = WAVE_MAPPER, status; DWORD mmpref = WAVE_MAPPER, compref = WAVE_MAPPER, status;
cubeb_device_pref ret = CUBEB_DEVICE_PREF_NONE; cubeb_device_pref ret = CUBEB_DEVICE_PREF_NONE;
if (waveOutMessage((HWAVEOUT) WAVE_MAPPER, DRVM_MAPPER_PREFERRED_GET, if (waveOutMessage((HWAVEOUT)WAVE_MAPPER, DRVM_MAPPER_PREFERRED_GET,
(DWORD_PTR)&mmpref, (DWORD_PTR)&status) == MMSYSERR_NOERROR && (DWORD_PTR)&mmpref,
(DWORD_PTR)&status) == MMSYSERR_NOERROR &&
devid == mmpref) devid == mmpref)
ret |= CUBEB_DEVICE_PREF_MULTIMEDIA | CUBEB_DEVICE_PREF_NOTIFICATION; ret |= CUBEB_DEVICE_PREF_MULTIMEDIA | CUBEB_DEVICE_PREF_NOTIFICATION;
if (waveOutMessage((HWAVEOUT) WAVE_MAPPER, DRVM_MAPPER_CONSOLEVOICECOM_GET, if (waveOutMessage((HWAVEOUT)WAVE_MAPPER, DRVM_MAPPER_CONSOLEVOICECOM_GET,
(DWORD_PTR)&compref, (DWORD_PTR)&status) == MMSYSERR_NOERROR && (DWORD_PTR)&compref,
(DWORD_PTR)&status) == MMSYSERR_NOERROR &&
devid == compref) devid == compref)
ret |= CUBEB_DEVICE_PREF_VOICE; ret |= CUBEB_DEVICE_PREF_VOICE;
@ -851,10 +958,11 @@ device_id_idx(UINT devid)
} }
static void static void
winmm_create_device_from_outcaps2(cubeb_device_info * ret, LPWAVEOUTCAPS2A caps, UINT devid) winmm_create_device_from_outcaps2(cubeb_device_info * ret, LPWAVEOUTCAPS2A caps,
UINT devid)
{ {
XASSERT(ret); XASSERT(ret);
ret->devid = (cubeb_devid) devid; ret->devid = (cubeb_devid)devid;
ret->device_id = device_id_idx(devid); ret->device_id = device_id_idx(devid);
ret->friendly_name = _strdup(caps->szPname); ret->friendly_name = _strdup(caps->szPname);
ret->group_id = guid_to_cstr(&caps->ProductGuid); ret->group_id = guid_to_cstr(&caps->ProductGuid);
@ -866,8 +974,8 @@ winmm_create_device_from_outcaps2(cubeb_device_info * ret, LPWAVEOUTCAPS2A caps,
ret->max_channels = caps->wChannels; ret->max_channels = caps->wChannels;
winmm_calculate_device_rate(ret, caps->dwFormats); winmm_calculate_device_rate(ret, caps->dwFormats);
winmm_query_supported_formats(devid, caps->dwFormats, winmm_query_supported_formats(devid, caps->dwFormats, &ret->format,
&ret->format, &ret->default_format); &ret->default_format);
/* Hardcoded latency estimates... */ /* Hardcoded latency estimates... */
ret->latency_lo = 100 * ret->default_rate / 1000; ret->latency_lo = 100 * ret->default_rate / 1000;
@ -875,10 +983,11 @@ winmm_create_device_from_outcaps2(cubeb_device_info * ret, LPWAVEOUTCAPS2A caps,
} }
static void static void
winmm_create_device_from_outcaps(cubeb_device_info * ret, LPWAVEOUTCAPSA caps, UINT devid) winmm_create_device_from_outcaps(cubeb_device_info * ret, LPWAVEOUTCAPSA caps,
UINT devid)
{ {
XASSERT(ret); XASSERT(ret);
ret->devid = (cubeb_devid) devid; ret->devid = (cubeb_devid)devid;
ret->device_id = device_id_idx(devid); ret->device_id = device_id_idx(devid);
ret->friendly_name = _strdup(caps->szPname); ret->friendly_name = _strdup(caps->szPname);
ret->group_id = NULL; ret->group_id = NULL;
@ -890,8 +999,8 @@ winmm_create_device_from_outcaps(cubeb_device_info * ret, LPWAVEOUTCAPSA caps, U
ret->max_channels = caps->wChannels; ret->max_channels = caps->wChannels;
winmm_calculate_device_rate(ret, caps->dwFormats); winmm_calculate_device_rate(ret, caps->dwFormats);
winmm_query_supported_formats(devid, caps->dwFormats, winmm_query_supported_formats(devid, caps->dwFormats, &ret->format,
&ret->format, &ret->default_format); &ret->default_format);
/* Hardcoded latency estimates... */ /* Hardcoded latency estimates... */
ret->latency_lo = 100 * ret->default_rate / 1000; ret->latency_lo = 100 * ret->default_rate / 1000;
@ -904,13 +1013,15 @@ winmm_query_preferred_in_device(UINT devid)
DWORD mmpref = WAVE_MAPPER, compref = WAVE_MAPPER, status; DWORD mmpref = WAVE_MAPPER, compref = WAVE_MAPPER, status;
cubeb_device_pref ret = CUBEB_DEVICE_PREF_NONE; cubeb_device_pref ret = CUBEB_DEVICE_PREF_NONE;
if (waveInMessage((HWAVEIN) WAVE_MAPPER, DRVM_MAPPER_PREFERRED_GET, if (waveInMessage((HWAVEIN)WAVE_MAPPER, DRVM_MAPPER_PREFERRED_GET,
(DWORD_PTR)&mmpref, (DWORD_PTR)&status) == MMSYSERR_NOERROR && (DWORD_PTR)&mmpref,
(DWORD_PTR)&status) == MMSYSERR_NOERROR &&
devid == mmpref) devid == mmpref)
ret |= CUBEB_DEVICE_PREF_MULTIMEDIA | CUBEB_DEVICE_PREF_NOTIFICATION; ret |= CUBEB_DEVICE_PREF_MULTIMEDIA | CUBEB_DEVICE_PREF_NOTIFICATION;
if (waveInMessage((HWAVEIN) WAVE_MAPPER, DRVM_MAPPER_CONSOLEVOICECOM_GET, if (waveInMessage((HWAVEIN)WAVE_MAPPER, DRVM_MAPPER_CONSOLEVOICECOM_GET,
(DWORD_PTR)&compref, (DWORD_PTR)&status) == MMSYSERR_NOERROR && (DWORD_PTR)&compref,
(DWORD_PTR)&status) == MMSYSERR_NOERROR &&
devid == compref) devid == compref)
ret |= CUBEB_DEVICE_PREF_VOICE; ret |= CUBEB_DEVICE_PREF_VOICE;
@ -918,10 +1029,11 @@ winmm_query_preferred_in_device(UINT devid)
} }
static void static void
winmm_create_device_from_incaps2(cubeb_device_info * ret, LPWAVEINCAPS2A caps, UINT devid) winmm_create_device_from_incaps2(cubeb_device_info * ret, LPWAVEINCAPS2A caps,
UINT devid)
{ {
XASSERT(ret); XASSERT(ret);
ret->devid = (cubeb_devid) devid; ret->devid = (cubeb_devid)devid;
ret->device_id = device_id_idx(devid); ret->device_id = device_id_idx(devid);
ret->friendly_name = _strdup(caps->szPname); ret->friendly_name = _strdup(caps->szPname);
ret->group_id = guid_to_cstr(&caps->ProductGuid); ret->group_id = guid_to_cstr(&caps->ProductGuid);
@ -933,8 +1045,8 @@ winmm_create_device_from_incaps2(cubeb_device_info * ret, LPWAVEINCAPS2A caps, U
ret->max_channels = caps->wChannels; ret->max_channels = caps->wChannels;
winmm_calculate_device_rate(ret, caps->dwFormats); winmm_calculate_device_rate(ret, caps->dwFormats);
winmm_query_supported_formats(devid, caps->dwFormats, winmm_query_supported_formats(devid, caps->dwFormats, &ret->format,
&ret->format, &ret->default_format); &ret->default_format);
/* Hardcoded latency estimates... */ /* Hardcoded latency estimates... */
ret->latency_lo = 100 * ret->default_rate / 1000; ret->latency_lo = 100 * ret->default_rate / 1000;
@ -942,10 +1054,11 @@ winmm_create_device_from_incaps2(cubeb_device_info * ret, LPWAVEINCAPS2A caps, U
} }
static void static void
winmm_create_device_from_incaps(cubeb_device_info * ret, LPWAVEINCAPSA caps, UINT devid) winmm_create_device_from_incaps(cubeb_device_info * ret, LPWAVEINCAPSA caps,
UINT devid)
{ {
XASSERT(ret); XASSERT(ret);
ret->devid = (cubeb_devid) devid; ret->devid = (cubeb_devid)devid;
ret->device_id = device_id_idx(devid); ret->device_id = device_id_idx(devid);
ret->friendly_name = _strdup(caps->szPname); ret->friendly_name = _strdup(caps->szPname);
ret->group_id = NULL; ret->group_id = NULL;
@ -957,8 +1070,8 @@ winmm_create_device_from_incaps(cubeb_device_info * ret, LPWAVEINCAPSA caps, UIN
ret->max_channels = caps->wChannels; ret->max_channels = caps->wChannels;
winmm_calculate_device_rate(ret, caps->dwFormats); winmm_calculate_device_rate(ret, caps->dwFormats);
winmm_query_supported_formats(devid, caps->dwFormats, winmm_query_supported_formats(devid, caps->dwFormats, &ret->format,
&ret->format, &ret->default_format); &ret->default_format);
/* Hardcoded latency estimates... */ /* Hardcoded latency estimates... */
ret->latency_lo = 100 * ret->default_rate / 1000; ret->latency_lo = 100 * ret->default_rate / 1000;
@ -989,7 +1102,8 @@ winmm_enumerate_devices(cubeb * context, cubeb_device_type type,
for (i = 0; i < outcount; i++) { for (i = 0; i < outcount; i++) {
dev = &devices[collection->count]; dev = &devices[collection->count];
if (waveOutGetDevCapsA(i, (LPWAVEOUTCAPSA)&woc2, sizeof(woc2)) == MMSYSERR_NOERROR) { if (waveOutGetDevCapsA(i, (LPWAVEOUTCAPSA)&woc2, sizeof(woc2)) ==
MMSYSERR_NOERROR) {
winmm_create_device_from_outcaps2(dev, &woc2, i); winmm_create_device_from_outcaps2(dev, &woc2, i);
collection->count += 1; collection->count += 1;
} else if (waveOutGetDevCapsA(i, &woc, sizeof(woc)) == MMSYSERR_NOERROR) { } else if (waveOutGetDevCapsA(i, &woc, sizeof(woc)) == MMSYSERR_NOERROR) {
@ -1008,7 +1122,8 @@ winmm_enumerate_devices(cubeb * context, cubeb_device_type type,
for (i = 0; i < incount; i++) { for (i = 0; i < incount; i++) {
dev = &devices[collection->count]; dev = &devices[collection->count];
if (waveInGetDevCapsA(i, (LPWAVEINCAPSA)&wic2, sizeof(wic2)) == MMSYSERR_NOERROR) { if (waveInGetDevCapsA(i, (LPWAVEINCAPSA)&wic2, sizeof(wic2)) ==
MMSYSERR_NOERROR) {
winmm_create_device_from_incaps2(dev, &wic2, i); winmm_create_device_from_incaps2(dev, &wic2, i);
collection->count += 1; collection->count += 1;
} else if (waveInGetDevCapsA(i, &wic, sizeof(wic)) == MMSYSERR_NOERROR) { } else if (waveInGetDevCapsA(i, &wic, sizeof(wic)) == MMSYSERR_NOERROR) {
@ -1030,13 +1145,13 @@ winmm_device_collection_destroy(cubeb * ctx,
uint32_t i; uint32_t i;
XASSERT(collection); XASSERT(collection);
(void) ctx; (void)ctx;
for (i = 0; i < collection->count; i++) { for (i = 0; i < collection->count; i++) {
free((void *) collection->device[i].device_id); free((void *)collection->device[i].device_id);
free((void *) collection->device[i].friendly_name); free((void *)collection->device[i].friendly_name);
free((void *) collection->device[i].group_id); free((void *)collection->device[i].group_id);
free((void *) collection->device[i].vendor_name); free((void *)collection->device[i].vendor_name);
} }
free(collection->device); free(collection->device);
@ -1044,26 +1159,24 @@ winmm_device_collection_destroy(cubeb * ctx,
} }
static struct cubeb_ops const winmm_ops = { static struct cubeb_ops const winmm_ops = {
/*.init =*/ winmm_init, /*.init =*/winmm_init,
/*.get_backend_id =*/ winmm_get_backend_id, /*.get_backend_id =*/winmm_get_backend_id,
/*.get_max_channel_count=*/ winmm_get_max_channel_count, /*.get_max_channel_count=*/winmm_get_max_channel_count,
/*.get_min_latency=*/ winmm_get_min_latency, /*.get_min_latency=*/winmm_get_min_latency,
/*.get_preferred_sample_rate =*/ winmm_get_preferred_sample_rate, /*.get_preferred_sample_rate =*/winmm_get_preferred_sample_rate,
/*.enumerate_devices =*/ winmm_enumerate_devices, /*.enumerate_devices =*/winmm_enumerate_devices,
/*.device_collection_destroy =*/ winmm_device_collection_destroy, /*.device_collection_destroy =*/winmm_device_collection_destroy,
/*.destroy =*/ winmm_destroy, /*.destroy =*/winmm_destroy,
/*.stream_init =*/ winmm_stream_init, /*.stream_init =*/winmm_stream_init,
/*.stream_destroy =*/ winmm_stream_destroy, /*.stream_destroy =*/winmm_stream_destroy,
/*.stream_start =*/ winmm_stream_start, /*.stream_start =*/winmm_stream_start,
/*.stream_stop =*/ winmm_stream_stop, /*.stream_stop =*/winmm_stream_stop,
/*.stream_reset_default_device =*/ NULL, /*.stream_get_position =*/winmm_stream_get_position,
/*.stream_get_position =*/ winmm_stream_get_position, /*.stream_get_latency = */ winmm_stream_get_latency,
/*.stream_get_latency = */ winmm_stream_get_latency, /*.stream_get_input_latency = */ NULL,
/*.stream_get_input_latency = */ NULL, /*.stream_set_volume =*/winmm_stream_set_volume,
/*.stream_set_volume =*/ winmm_stream_set_volume, /*.stream_set_name =*/NULL,
/*.stream_set_name =*/ NULL, /*.stream_get_current_device =*/NULL,
/*.stream_get_current_device =*/ NULL, /*.stream_device_destroy =*/NULL,
/*.stream_device_destroy =*/ NULL, /*.stream_register_device_changed_callback=*/NULL,
/*.stream_register_device_changed_callback=*/ NULL, /*.register_device_collection_changed =*/NULL};
/*.register_device_collection_changed =*/ NULL
};

235
externals/cubeb/subprojects/speex/arch.h vendored Executable file
View file

@ -0,0 +1,235 @@
/* Copyright (C) 2003 Jean-Marc Valin */
/**
@file arch.h
@brief Various architecture definitions Speex
*/
/*
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
- Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
- Neither the name of the Xiph.org Foundation 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 FOUNDATION 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.
*/
#ifndef ARCH_H
#define ARCH_H
/* A couple test to catch stupid option combinations */
#ifdef FIXED_POINT
#ifdef FLOATING_POINT
#error You cannot compile as floating point and fixed point at the same time
#endif
#ifdef _USE_SSE
#error SSE is only for floating-point
#endif
#if ((defined (ARM4_ASM)||defined (ARM4_ASM)) && defined(BFIN_ASM)) || (defined (ARM4_ASM)&&defined(ARM5E_ASM))
#error Make up your mind. What CPU do you have?
#endif
#ifdef VORBIS_PSYCHO
#error Vorbis-psy model currently not implemented in fixed-point
#endif
#else
#ifndef FLOATING_POINT
#error You now need to define either FIXED_POINT or FLOATING_POINT
#endif
#if defined (ARM4_ASM) || defined(ARM5E_ASM) || defined(BFIN_ASM)
#error I suppose you can have a [ARM4/ARM5E/Blackfin] that has float instructions?
#endif
#ifdef FIXED_POINT_DEBUG
#error "Don't you think enabling fixed-point is a good thing to do if you want to debug that?"
#endif
#endif
#ifndef OUTSIDE_SPEEX
#include "speex/speexdsp_types.h"
#endif
#define ABS(x) ((x) < 0 ? (-(x)) : (x)) /**< Absolute integer value. */
#define ABS16(x) ((x) < 0 ? (-(x)) : (x)) /**< Absolute 16-bit value. */
#define MIN16(a,b) ((a) < (b) ? (a) : (b)) /**< Maximum 16-bit value. */
#define MAX16(a,b) ((a) > (b) ? (a) : (b)) /**< Maximum 16-bit value. */
#define ABS32(x) ((x) < 0 ? (-(x)) : (x)) /**< Absolute 32-bit value. */
#define MIN32(a,b) ((a) < (b) ? (a) : (b)) /**< Maximum 32-bit value. */
#define MAX32(a,b) ((a) > (b) ? (a) : (b)) /**< Maximum 32-bit value. */
#ifdef FIXED_POINT
typedef spx_int16_t spx_word16_t;
typedef spx_int32_t spx_word32_t;
typedef spx_word32_t spx_mem_t;
typedef spx_word16_t spx_coef_t;
typedef spx_word16_t spx_lsp_t;
typedef spx_word32_t spx_sig_t;
#define Q15ONE 32767
#define LPC_SCALING 8192
#define SIG_SCALING 16384
#define LSP_SCALING 8192.
#define GAMMA_SCALING 32768.
#define GAIN_SCALING 64
#define GAIN_SCALING_1 0.015625
#define LPC_SHIFT 13
#define LSP_SHIFT 13
#define SIG_SHIFT 14
#define GAIN_SHIFT 6
#define WORD2INT(x) ((x) < -32767 ? -32768 : ((x) > 32766 ? 32767 : (x)))
#define VERY_SMALL 0
#define VERY_LARGE32 ((spx_word32_t)2147483647)
#define VERY_LARGE16 ((spx_word16_t)32767)
#define Q15_ONE ((spx_word16_t)32767)
#ifdef FIXED_DEBUG
#include "fixed_debug.h"
#else
#include "fixed_generic.h"
#ifdef ARM5E_ASM
#include "fixed_arm5e.h"
#elif defined (ARM4_ASM)
#include "fixed_arm4.h"
#elif defined (BFIN_ASM)
#include "fixed_bfin.h"
#endif
#endif
#else
typedef float spx_mem_t;
typedef float spx_coef_t;
typedef float spx_lsp_t;
typedef float spx_sig_t;
typedef float spx_word16_t;
typedef float spx_word32_t;
#define Q15ONE 1.0f
#define LPC_SCALING 1.f
#define SIG_SCALING 1.f
#define LSP_SCALING 1.f
#define GAMMA_SCALING 1.f
#define GAIN_SCALING 1.f
#define GAIN_SCALING_1 1.f
#define VERY_SMALL 1e-15f
#define VERY_LARGE32 1e15f
#define VERY_LARGE16 1e15f
#define Q15_ONE ((spx_word16_t)1.f)
#define QCONST16(x,bits) (x)
#define QCONST32(x,bits) (x)
#define NEG16(x) (-(x))
#define NEG32(x) (-(x))
#define EXTRACT16(x) (x)
#define EXTEND32(x) (x)
#define SHR16(a,shift) (a)
#define SHL16(a,shift) (a)
#define SHR32(a,shift) (a)
#define SHL32(a,shift) (a)
#define PSHR16(a,shift) (a)
#define PSHR32(a,shift) (a)
#define VSHR32(a,shift) (a)
#define SATURATE16(x,a) (x)
#define SATURATE32(x,a) (x)
#define SATURATE32PSHR(x,shift,a) (x)
#define PSHR(a,shift) (a)
#define SHR(a,shift) (a)
#define SHL(a,shift) (a)
#define SATURATE(x,a) (x)
#define ADD16(a,b) ((a)+(b))
#define SUB16(a,b) ((a)-(b))
#define ADD32(a,b) ((a)+(b))
#define SUB32(a,b) ((a)-(b))
#define MULT16_16_16(a,b) ((a)*(b))
#define MULT16_16(a,b) ((spx_word32_t)(a)*(spx_word32_t)(b))
#define MAC16_16(c,a,b) ((c)+(spx_word32_t)(a)*(spx_word32_t)(b))
#define MULT16_32_Q11(a,b) ((a)*(b))
#define MULT16_32_Q13(a,b) ((a)*(b))
#define MULT16_32_Q14(a,b) ((a)*(b))
#define MULT16_32_Q15(a,b) ((a)*(b))
#define MULT16_32_P15(a,b) ((a)*(b))
#define MAC16_32_Q11(c,a,b) ((c)+(a)*(b))
#define MAC16_32_Q15(c,a,b) ((c)+(a)*(b))
#define MAC16_16_Q11(c,a,b) ((c)+(a)*(b))
#define MAC16_16_Q13(c,a,b) ((c)+(a)*(b))
#define MAC16_16_P13(c,a,b) ((c)+(a)*(b))
#define MULT16_16_Q11_32(a,b) ((a)*(b))
#define MULT16_16_Q13(a,b) ((a)*(b))
#define MULT16_16_Q14(a,b) ((a)*(b))
#define MULT16_16_Q15(a,b) ((a)*(b))
#define MULT16_16_P15(a,b) ((a)*(b))
#define MULT16_16_P13(a,b) ((a)*(b))
#define MULT16_16_P14(a,b) ((a)*(b))
#define DIV32_16(a,b) (((spx_word32_t)(a))/(spx_word16_t)(b))
#define PDIV32_16(a,b) (((spx_word32_t)(a))/(spx_word16_t)(b))
#define DIV32(a,b) (((spx_word32_t)(a))/(spx_word32_t)(b))
#define PDIV32(a,b) (((spx_word32_t)(a))/(spx_word32_t)(b))
#define WORD2INT(x) ((x) < -32767.5f ? -32768 : \
((x) > 32766.5f ? 32767 : (spx_int16_t)floor(.5 + (x))))
#endif
#if defined (CONFIG_TI_C54X) || defined (CONFIG_TI_C55X)
/* 2 on TI C5x DSP */
#define BYTES_PER_CHAR 2
#define BITS_PER_CHAR 16
#define LOG2_BITS_PER_CHAR 4
#else
#define BYTES_PER_CHAR 1
#define BITS_PER_CHAR 8
#define LOG2_BITS_PER_CHAR 3
#endif
#ifdef FIXED_DEBUG
extern long long spx_mips;
#endif
#endif

View file

@ -0,0 +1,110 @@
/* Copyright (C) 2003 Jean-Marc Valin */
/**
@file fixed_generic.h
@brief Generic fixed-point operations
*/
/*
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
- Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
- Neither the name of the Xiph.org Foundation 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 FOUNDATION 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.
*/
#ifndef FIXED_GENERIC_H
#define FIXED_GENERIC_H
#define QCONST16(x,bits) ((spx_word16_t)(.5+(x)*(((spx_word32_t)1)<<(bits))))
#define QCONST32(x,bits) ((spx_word32_t)(.5+(x)*(((spx_word32_t)1)<<(bits))))
#define NEG16(x) (-(x))
#define NEG32(x) (-(x))
#define EXTRACT16(x) ((spx_word16_t)(x))
#define EXTEND32(x) ((spx_word32_t)(x))
#define SHR16(a,shift) ((a) >> (shift))
#define SHL16(a,shift) ((a) << (shift))
#define SHR32(a,shift) ((a) >> (shift))
#define SHL32(a,shift) ((a) << (shift))
#define PSHR16(a,shift) (SHR16((a)+((1<<((shift))>>1)),shift))
#define PSHR32(a,shift) (SHR32((a)+((EXTEND32(1)<<((shift))>>1)),shift))
#define VSHR32(a, shift) (((shift)>0) ? SHR32(a, shift) : SHL32(a, -(shift)))
#define SATURATE16(x,a) (((x)>(a) ? (a) : (x)<-(a) ? -(a) : (x)))
#define SATURATE32(x,a) (((x)>(a) ? (a) : (x)<-(a) ? -(a) : (x)))
#define SATURATE32PSHR(x,shift,a) (((x)>=(SHL32(a,shift))) ? (a) : \
(x)<=-(SHL32(a,shift)) ? -(a) : \
(PSHR32(x, shift)))
#define SHR(a,shift) ((a) >> (shift))
#define SHL(a,shift) ((spx_word32_t)(a) << (shift))
#define PSHR(a,shift) (SHR((a)+((EXTEND32(1)<<((shift))>>1)),shift))
#define SATURATE(x,a) (((x)>(a) ? (a) : (x)<-(a) ? -(a) : (x)))
#define ADD16(a,b) ((spx_word16_t)((spx_word16_t)(a)+(spx_word16_t)(b)))
#define SUB16(a,b) ((spx_word16_t)(a)-(spx_word16_t)(b))
#define ADD32(a,b) ((spx_word32_t)(a)+(spx_word32_t)(b))
#define SUB32(a,b) ((spx_word32_t)(a)-(spx_word32_t)(b))
/* result fits in 16 bits */
#define MULT16_16_16(a,b) ((((spx_word16_t)(a))*((spx_word16_t)(b))))
/* (spx_word32_t)(spx_word16_t) gives TI compiler a hint that it's 16x16->32 multiply */
#define MULT16_16(a,b) (((spx_word32_t)(spx_word16_t)(a))*((spx_word32_t)(spx_word16_t)(b)))
#define MAC16_16(c,a,b) (ADD32((c),MULT16_16((a),(b))))
#define MULT16_32_Q12(a,b) ADD32(MULT16_16((a),SHR((b),12)), SHR(MULT16_16((a),((b)&0x00000fff)),12))
#define MULT16_32_Q13(a,b) ADD32(MULT16_16((a),SHR((b),13)), SHR(MULT16_16((a),((b)&0x00001fff)),13))
#define MULT16_32_Q14(a,b) ADD32(MULT16_16((a),SHR((b),14)), SHR(MULT16_16((a),((b)&0x00003fff)),14))
#define MULT16_32_Q11(a,b) ADD32(MULT16_16((a),SHR((b),11)), SHR(MULT16_16((a),((b)&0x000007ff)),11))
#define MAC16_32_Q11(c,a,b) ADD32(c,ADD32(MULT16_16((a),SHR((b),11)), SHR(MULT16_16((a),((b)&0x000007ff)),11)))
#define MULT16_32_P15(a,b) ADD32(MULT16_16((a),SHR((b),15)), PSHR(MULT16_16((a),((b)&0x00007fff)),15))
#define MULT16_32_Q15(a,b) ADD32(MULT16_16((a),SHR((b),15)), SHR(MULT16_16((a),((b)&0x00007fff)),15))
#define MAC16_32_Q15(c,a,b) ADD32(c,ADD32(MULT16_16((a),SHR((b),15)), SHR(MULT16_16((a),((b)&0x00007fff)),15)))
#define MAC16_16_Q11(c,a,b) (ADD32((c),SHR(MULT16_16((a),(b)),11)))
#define MAC16_16_Q13(c,a,b) (ADD32((c),SHR(MULT16_16((a),(b)),13)))
#define MAC16_16_P13(c,a,b) (ADD32((c),SHR(ADD32(4096,MULT16_16((a),(b))),13)))
#define MULT16_16_Q11_32(a,b) (SHR(MULT16_16((a),(b)),11))
#define MULT16_16_Q13(a,b) (SHR(MULT16_16((a),(b)),13))
#define MULT16_16_Q14(a,b) (SHR(MULT16_16((a),(b)),14))
#define MULT16_16_Q15(a,b) (SHR(MULT16_16((a),(b)),15))
#define MULT16_16_P13(a,b) (SHR(ADD32(4096,MULT16_16((a),(b))),13))
#define MULT16_16_P14(a,b) (SHR(ADD32(8192,MULT16_16((a),(b))),14))
#define MULT16_16_P15(a,b) (SHR(ADD32(16384,MULT16_16((a),(b))),15))
#define MUL_16_32_R15(a,bh,bl) ADD32(MULT16_16((a),(bh)), SHR(MULT16_16((a),(bl)),15))
#define DIV32_16(a,b) ((spx_word16_t)(((spx_word32_t)(a))/((spx_word16_t)(b))))
#define PDIV32_16(a,b) ((spx_word16_t)(((spx_word32_t)(a)+((spx_word16_t)(b)>>1))/((spx_word16_t)(b))))
#define DIV32(a,b) (((spx_word32_t)(a))/((spx_word32_t)(b)))
#define PDIV32(a,b) (((spx_word32_t)(a)+((spx_word16_t)(b)>>1))/((spx_word32_t)(b)))
#endif

1240
externals/cubeb/subprojects/speex/resample.c vendored Executable file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,201 @@
/* Copyright (C) 2007-2008 Jean-Marc Valin
* Copyright (C) 2008 Thorvald Natvig
* Copyright (C) 2011 Texas Instruments
* author Jyri Sarha
*/
/**
@file resample_neon.h
@brief Resampler functions (NEON version)
*/
/*
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
- Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
- Neither the name of the Xiph.org Foundation 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 FOUNDATION OR
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include <arm_neon.h>
#ifdef FIXED_POINT
#ifdef __thumb2__
static inline int32_t saturate_32bit_to_16bit(int32_t a) {
int32_t ret;
asm ("ssat %[ret], #16, %[a]"
: [ret] "=&r" (ret)
: [a] "r" (a)
: );
return ret;
}
#else
static inline int32_t saturate_32bit_to_16bit(int32_t a) {
int32_t ret;
asm ("vmov.s32 d0[0], %[a]\n"
"vqmovn.s32 d0, q0\n"
"vmov.s16 %[ret], d0[0]\n"
: [ret] "=&r" (ret)
: [a] "r" (a)
: "q0");
return ret;
}
#endif
#undef WORD2INT
#define WORD2INT(x) (saturate_32bit_to_16bit(x))
#define OVERRIDE_INNER_PRODUCT_SINGLE
/* Only works when len % 4 == 0 */
static inline int32_t inner_product_single(const int16_t *a, const int16_t *b, unsigned int len)
{
int32_t ret;
uint32_t remainder = len % 16;
len = len - remainder;
asm volatile (" cmp %[len], #0\n"
" bne 1f\n"
" vld1.16 {d16}, [%[b]]!\n"
" vld1.16 {d20}, [%[a]]!\n"
" subs %[remainder], %[remainder], #4\n"
" vmull.s16 q0, d16, d20\n"
" beq 5f\n"
" b 4f\n"
"1:"
" vld1.16 {d16, d17, d18, d19}, [%[b]]!\n"
" vld1.16 {d20, d21, d22, d23}, [%[a]]!\n"
" subs %[len], %[len], #16\n"
" vmull.s16 q0, d16, d20\n"
" vmlal.s16 q0, d17, d21\n"
" vmlal.s16 q0, d18, d22\n"
" vmlal.s16 q0, d19, d23\n"
" beq 3f\n"
"2:"
" vld1.16 {d16, d17, d18, d19}, [%[b]]!\n"
" vld1.16 {d20, d21, d22, d23}, [%[a]]!\n"
" subs %[len], %[len], #16\n"
" vmlal.s16 q0, d16, d20\n"
" vmlal.s16 q0, d17, d21\n"
" vmlal.s16 q0, d18, d22\n"
" vmlal.s16 q0, d19, d23\n"
" bne 2b\n"
"3:"
" cmp %[remainder], #0\n"
" beq 5f\n"
"4:"
" vld1.16 {d16}, [%[b]]!\n"
" vld1.16 {d20}, [%[a]]!\n"
" subs %[remainder], %[remainder], #4\n"
" vmlal.s16 q0, d16, d20\n"
" bne 4b\n"
"5:"
" vaddl.s32 q0, d0, d1\n"
" vadd.s64 d0, d0, d1\n"
" vqmovn.s64 d0, q0\n"
" vqrshrn.s32 d0, q0, #15\n"
" vmov.s16 %[ret], d0[0]\n"
: [ret] "=&r" (ret), [a] "+r" (a), [b] "+r" (b),
[len] "+r" (len), [remainder] "+r" (remainder)
:
: "cc", "q0",
"d16", "d17", "d18", "d19",
"d20", "d21", "d22", "d23");
return ret;
}
#elif defined(FLOATING_POINT)
static inline int32_t saturate_float_to_16bit(float a) {
int32_t ret;
asm ("vmov.f32 d0[0], %[a]\n"
"vcvt.s32.f32 d0, d0, #15\n"
"vqrshrn.s32 d0, q0, #15\n"
"vmov.s16 %[ret], d0[0]\n"
: [ret] "=&r" (ret)
: [a] "r" (a)
: "q0");
return ret;
}
#undef WORD2INT
#define WORD2INT(x) (saturate_float_to_16bit(x))
#define OVERRIDE_INNER_PRODUCT_SINGLE
/* Only works when len % 4 == 0 */
static inline float inner_product_single(const float *a, const float *b, unsigned int len)
{
float ret;
uint32_t remainder = len % 16;
len = len - remainder;
asm volatile (" cmp %[len], #0\n"
" bne 1f\n"
" vld1.32 {q4}, [%[b]]!\n"
" vld1.32 {q8}, [%[a]]!\n"
" subs %[remainder], %[remainder], #4\n"
" vmul.f32 q0, q4, q8\n"
" bne 4f\n"
" b 5f\n"
"1:"
" vld1.32 {q4, q5}, [%[b]]!\n"
" vld1.32 {q8, q9}, [%[a]]!\n"
" vld1.32 {q6, q7}, [%[b]]!\n"
" vld1.32 {q10, q11}, [%[a]]!\n"
" subs %[len], %[len], #16\n"
" vmul.f32 q0, q4, q8\n"
" vmul.f32 q1, q5, q9\n"
" vmul.f32 q2, q6, q10\n"
" vmul.f32 q3, q7, q11\n"
" beq 3f\n"
"2:"
" vld1.32 {q4, q5}, [%[b]]!\n"
" vld1.32 {q8, q9}, [%[a]]!\n"
" vld1.32 {q6, q7}, [%[b]]!\n"
" vld1.32 {q10, q11}, [%[a]]!\n"
" subs %[len], %[len], #16\n"
" vmla.f32 q0, q4, q8\n"
" vmla.f32 q1, q5, q9\n"
" vmla.f32 q2, q6, q10\n"
" vmla.f32 q3, q7, q11\n"
" bne 2b\n"
"3:"
" vadd.f32 q4, q0, q1\n"
" vadd.f32 q5, q2, q3\n"
" cmp %[remainder], #0\n"
" vadd.f32 q0, q4, q5\n"
" beq 5f\n"
"4:"
" vld1.32 {q6}, [%[b]]!\n"
" vld1.32 {q10}, [%[a]]!\n"
" subs %[remainder], %[remainder], #4\n"
" vmla.f32 q0, q6, q10\n"
" bne 4b\n"
"5:"
" vadd.f32 d0, d0, d1\n"
" vpadd.f32 d0, d0, d0\n"
" vmov.f32 %[ret], d0[0]\n"
: [ret] "=&r" (ret), [a] "+r" (a), [b] "+r" (b),
[len] "+l" (len), [remainder] "+l" (remainder)
:
: "cc", "q0", "q1", "q2", "q3", "q4", "q5", "q6", "q7", "q8",
"q9", "q10", "q11");
return ret;
}
#endif

View file

@ -0,0 +1,128 @@
/* Copyright (C) 2007-2008 Jean-Marc Valin
* Copyright (C) 2008 Thorvald Natvig
*/
/**
@file resample_sse.h
@brief Resampler functions (SSE version)
*/
/*
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
- Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
- Neither the name of the Xiph.org Foundation 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 FOUNDATION OR
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include <xmmintrin.h>
#define OVERRIDE_INNER_PRODUCT_SINGLE
static inline float inner_product_single(const float *a, const float *b, unsigned int len)
{
int i;
float ret;
__m128 sum = _mm_setzero_ps();
for (i=0;i<len;i+=8)
{
sum = _mm_add_ps(sum, _mm_mul_ps(_mm_loadu_ps(a+i), _mm_loadu_ps(b+i)));
sum = _mm_add_ps(sum, _mm_mul_ps(_mm_loadu_ps(a+i+4), _mm_loadu_ps(b+i+4)));
}
sum = _mm_add_ps(sum, _mm_movehl_ps(sum, sum));
sum = _mm_add_ss(sum, _mm_shuffle_ps(sum, sum, 0x55));
_mm_store_ss(&ret, sum);
return ret;
}
#define OVERRIDE_INTERPOLATE_PRODUCT_SINGLE
static inline float interpolate_product_single(const float *a, const float *b, unsigned int len, const spx_uint32_t oversample, float *frac) {
int i;
float ret;
__m128 sum = _mm_setzero_ps();
__m128 f = _mm_loadu_ps(frac);
for(i=0;i<len;i+=2)
{
sum = _mm_add_ps(sum, _mm_mul_ps(_mm_load1_ps(a+i), _mm_loadu_ps(b+i*oversample)));
sum = _mm_add_ps(sum, _mm_mul_ps(_mm_load1_ps(a+i+1), _mm_loadu_ps(b+(i+1)*oversample)));
}
sum = _mm_mul_ps(f, sum);
sum = _mm_add_ps(sum, _mm_movehl_ps(sum, sum));
sum = _mm_add_ss(sum, _mm_shuffle_ps(sum, sum, 0x55));
_mm_store_ss(&ret, sum);
return ret;
}
#ifdef _USE_SSE2
#include <emmintrin.h>
#define OVERRIDE_INNER_PRODUCT_DOUBLE
static inline double inner_product_double(const float *a, const float *b, unsigned int len)
{
int i;
double ret;
__m128d sum = _mm_setzero_pd();
__m128 t;
for (i=0;i<len;i+=8)
{
t = _mm_mul_ps(_mm_loadu_ps(a+i), _mm_loadu_ps(b+i));
sum = _mm_add_pd(sum, _mm_cvtps_pd(t));
sum = _mm_add_pd(sum, _mm_cvtps_pd(_mm_movehl_ps(t, t)));
t = _mm_mul_ps(_mm_loadu_ps(a+i+4), _mm_loadu_ps(b+i+4));
sum = _mm_add_pd(sum, _mm_cvtps_pd(t));
sum = _mm_add_pd(sum, _mm_cvtps_pd(_mm_movehl_ps(t, t)));
}
sum = _mm_add_sd(sum, _mm_unpackhi_pd(sum, sum));
_mm_store_sd(&ret, sum);
return ret;
}
#define OVERRIDE_INTERPOLATE_PRODUCT_DOUBLE
static inline double interpolate_product_double(const float *a, const float *b, unsigned int len, const spx_uint32_t oversample, float *frac) {
int i;
double ret;
__m128d sum;
__m128d sum1 = _mm_setzero_pd();
__m128d sum2 = _mm_setzero_pd();
__m128 f = _mm_loadu_ps(frac);
__m128d f1 = _mm_cvtps_pd(f);
__m128d f2 = _mm_cvtps_pd(_mm_movehl_ps(f,f));
__m128 t;
for(i=0;i<len;i+=2)
{
t = _mm_mul_ps(_mm_load1_ps(a+i), _mm_loadu_ps(b+i*oversample));
sum1 = _mm_add_pd(sum1, _mm_cvtps_pd(t));
sum2 = _mm_add_pd(sum2, _mm_cvtps_pd(_mm_movehl_ps(t, t)));
t = _mm_mul_ps(_mm_load1_ps(a+i+1), _mm_loadu_ps(b+(i+1)*oversample));
sum1 = _mm_add_pd(sum1, _mm_cvtps_pd(t));
sum2 = _mm_add_pd(sum2, _mm_cvtps_pd(_mm_movehl_ps(t, t)));
}
sum1 = _mm_mul_pd(f1, sum1);
sum2 = _mm_mul_pd(f2, sum2);
sum = _mm_add_pd(sum1, sum2);
sum = _mm_add_sd(sum, _mm_unpackhi_pd(sum, sum));
_mm_store_sd(&ret, sum);
return ret;
}
#endif

View file

@ -0,0 +1,10 @@
#ifndef __SPEEX_TYPES_H__
#define __SPEEX_TYPES_H__
/* these are filled in by configure */
typedef int16_t spx_int16_t;
typedef uint16_t spx_uint16_t;
typedef int32_t spx_int32_t;
typedef uint32_t spx_uint32_t;
#endif

View file

@ -0,0 +1,343 @@
/* Copyright (C) 2007 Jean-Marc Valin
File: speex_resampler.h
Resampling code
The design goals of this code are:
- Very fast algorithm
- Low memory requirement
- Good *perceptual* quality (and not best SNR)
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. The name of the author may not be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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.
*/
#ifndef SPEEX_RESAMPLER_H
#define SPEEX_RESAMPLER_H
#ifdef OUTSIDE_SPEEX
/********* WARNING: MENTAL SANITY ENDS HERE *************/
/* If the resampler is defined outside of Speex, we change the symbol names so that
there won't be any clash if linking with Speex later on. */
/* #define RANDOM_PREFIX your software name here */
#ifndef RANDOM_PREFIX
#error "Please define RANDOM_PREFIX (above) to something specific to your project to prevent symbol name clashes"
#endif
#define CAT_PREFIX2(a,b) a ## b
#define CAT_PREFIX(a,b) CAT_PREFIX2(a, b)
#define speex_resampler_init CAT_PREFIX(RANDOM_PREFIX,_resampler_init)
#define speex_resampler_init_frac CAT_PREFIX(RANDOM_PREFIX,_resampler_init_frac)
#define speex_resampler_destroy CAT_PREFIX(RANDOM_PREFIX,_resampler_destroy)
#define speex_resampler_process_float CAT_PREFIX(RANDOM_PREFIX,_resampler_process_float)
#define speex_resampler_process_int CAT_PREFIX(RANDOM_PREFIX,_resampler_process_int)
#define speex_resampler_process_interleaved_float CAT_PREFIX(RANDOM_PREFIX,_resampler_process_interleaved_float)
#define speex_resampler_process_interleaved_int CAT_PREFIX(RANDOM_PREFIX,_resampler_process_interleaved_int)
#define speex_resampler_set_rate CAT_PREFIX(RANDOM_PREFIX,_resampler_set_rate)
#define speex_resampler_get_rate CAT_PREFIX(RANDOM_PREFIX,_resampler_get_rate)
#define speex_resampler_set_rate_frac CAT_PREFIX(RANDOM_PREFIX,_resampler_set_rate_frac)
#define speex_resampler_get_ratio CAT_PREFIX(RANDOM_PREFIX,_resampler_get_ratio)
#define speex_resampler_set_quality CAT_PREFIX(RANDOM_PREFIX,_resampler_set_quality)
#define speex_resampler_get_quality CAT_PREFIX(RANDOM_PREFIX,_resampler_get_quality)
#define speex_resampler_set_input_stride CAT_PREFIX(RANDOM_PREFIX,_resampler_set_input_stride)
#define speex_resampler_get_input_stride CAT_PREFIX(RANDOM_PREFIX,_resampler_get_input_stride)
#define speex_resampler_set_output_stride CAT_PREFIX(RANDOM_PREFIX,_resampler_set_output_stride)
#define speex_resampler_get_output_stride CAT_PREFIX(RANDOM_PREFIX,_resampler_get_output_stride)
#define speex_resampler_get_input_latency CAT_PREFIX(RANDOM_PREFIX,_resampler_get_input_latency)
#define speex_resampler_get_output_latency CAT_PREFIX(RANDOM_PREFIX,_resampler_get_output_latency)
#define speex_resampler_skip_zeros CAT_PREFIX(RANDOM_PREFIX,_resampler_skip_zeros)
#define speex_resampler_reset_mem CAT_PREFIX(RANDOM_PREFIX,_resampler_reset_mem)
#define speex_resampler_strerror CAT_PREFIX(RANDOM_PREFIX,_resampler_strerror)
#define spx_int16_t short
#define spx_int32_t int
#define spx_uint16_t unsigned short
#define spx_uint32_t unsigned int
#define speex_assert(cond)
#else /* OUTSIDE_SPEEX */
#include "speexdsp_types.h"
#endif /* OUTSIDE_SPEEX */
#ifdef __cplusplus
extern "C" {
#endif
#define SPEEX_RESAMPLER_QUALITY_MAX 10
#define SPEEX_RESAMPLER_QUALITY_MIN 0
#define SPEEX_RESAMPLER_QUALITY_DEFAULT 4
#define SPEEX_RESAMPLER_QUALITY_VOIP 3
#define SPEEX_RESAMPLER_QUALITY_DESKTOP 5
enum {
RESAMPLER_ERR_SUCCESS = 0,
RESAMPLER_ERR_ALLOC_FAILED = 1,
RESAMPLER_ERR_BAD_STATE = 2,
RESAMPLER_ERR_INVALID_ARG = 3,
RESAMPLER_ERR_PTR_OVERLAP = 4,
RESAMPLER_ERR_OVERFLOW = 5,
RESAMPLER_ERR_MAX_ERROR
};
struct SpeexResamplerState_;
typedef struct SpeexResamplerState_ SpeexResamplerState;
/** Create a new resampler with integer input and output rates.
* @param nb_channels Number of channels to be processed
* @param in_rate Input sampling rate (integer number of Hz).
* @param out_rate Output sampling rate (integer number of Hz).
* @param quality Resampling quality between 0 and 10, where 0 has poor quality
* and 10 has very high quality.
* @return Newly created resampler state
* @retval NULL Error: not enough memory
*/
SpeexResamplerState *speex_resampler_init(spx_uint32_t nb_channels,
spx_uint32_t in_rate,
spx_uint32_t out_rate,
int quality,
int *err);
/** Create a new resampler with fractional input/output rates. The sampling
* rate ratio is an arbitrary rational number with both the numerator and
* denominator being 32-bit integers.
* @param nb_channels Number of channels to be processed
* @param ratio_num Numerator of the sampling rate ratio
* @param ratio_den Denominator of the sampling rate ratio
* @param in_rate Input sampling rate rounded to the nearest integer (in Hz).
* @param out_rate Output sampling rate rounded to the nearest integer (in Hz).
* @param quality Resampling quality between 0 and 10, where 0 has poor quality
* and 10 has very high quality.
* @return Newly created resampler state
* @retval NULL Error: not enough memory
*/
SpeexResamplerState *speex_resampler_init_frac(spx_uint32_t nb_channels,
spx_uint32_t ratio_num,
spx_uint32_t ratio_den,
spx_uint32_t in_rate,
spx_uint32_t out_rate,
int quality,
int *err);
/** Destroy a resampler state.
* @param st Resampler state
*/
void speex_resampler_destroy(SpeexResamplerState *st);
/** Resample a float array. The input and output buffers must *not* overlap.
* @param st Resampler state
* @param channel_index Index of the channel to process for the multi-channel
* base (0 otherwise)
* @param in Input buffer
* @param in_len Number of input samples in the input buffer. Returns the
* number of samples processed
* @param out Output buffer
* @param out_len Size of the output buffer. Returns the number of samples written
*/
int speex_resampler_process_float(SpeexResamplerState *st,
spx_uint32_t channel_index,
const float *in,
spx_uint32_t *in_len,
float *out,
spx_uint32_t *out_len);
/** Resample an int array. The input and output buffers must *not* overlap.
* @param st Resampler state
* @param channel_index Index of the channel to process for the multi-channel
* base (0 otherwise)
* @param in Input buffer
* @param in_len Number of input samples in the input buffer. Returns the number
* of samples processed
* @param out Output buffer
* @param out_len Size of the output buffer. Returns the number of samples written
*/
int speex_resampler_process_int(SpeexResamplerState *st,
spx_uint32_t channel_index,
const spx_int16_t *in,
spx_uint32_t *in_len,
spx_int16_t *out,
spx_uint32_t *out_len);
/** Resample an interleaved float array. The input and output buffers must *not* overlap.
* @param st Resampler state
* @param in Input buffer
* @param in_len Number of input samples in the input buffer. Returns the number
* of samples processed. This is all per-channel.
* @param out Output buffer
* @param out_len Size of the output buffer. Returns the number of samples written.
* This is all per-channel.
*/
int speex_resampler_process_interleaved_float(SpeexResamplerState *st,
const float *in,
spx_uint32_t *in_len,
float *out,
spx_uint32_t *out_len);
/** Resample an interleaved int array. The input and output buffers must *not* overlap.
* @param st Resampler state
* @param in Input buffer
* @param in_len Number of input samples in the input buffer. Returns the number
* of samples processed. This is all per-channel.
* @param out Output buffer
* @param out_len Size of the output buffer. Returns the number of samples written.
* This is all per-channel.
*/
int speex_resampler_process_interleaved_int(SpeexResamplerState *st,
const spx_int16_t *in,
spx_uint32_t *in_len,
spx_int16_t *out,
spx_uint32_t *out_len);
/** Set (change) the input/output sampling rates (integer value).
* @param st Resampler state
* @param in_rate Input sampling rate (integer number of Hz).
* @param out_rate Output sampling rate (integer number of Hz).
*/
int speex_resampler_set_rate(SpeexResamplerState *st,
spx_uint32_t in_rate,
spx_uint32_t out_rate);
/** Get the current input/output sampling rates (integer value).
* @param st Resampler state
* @param in_rate Input sampling rate (integer number of Hz) copied.
* @param out_rate Output sampling rate (integer number of Hz) copied.
*/
void speex_resampler_get_rate(SpeexResamplerState *st,
spx_uint32_t *in_rate,
spx_uint32_t *out_rate);
/** Set (change) the input/output sampling rates and resampling ratio
* (fractional values in Hz supported).
* @param st Resampler state
* @param ratio_num Numerator of the sampling rate ratio
* @param ratio_den Denominator of the sampling rate ratio
* @param in_rate Input sampling rate rounded to the nearest integer (in Hz).
* @param out_rate Output sampling rate rounded to the nearest integer (in Hz).
*/
int speex_resampler_set_rate_frac(SpeexResamplerState *st,
spx_uint32_t ratio_num,
spx_uint32_t ratio_den,
spx_uint32_t in_rate,
spx_uint32_t out_rate);
/** Get the current resampling ratio. This will be reduced to the least
* common denominator.
* @param st Resampler state
* @param ratio_num Numerator of the sampling rate ratio copied
* @param ratio_den Denominator of the sampling rate ratio copied
*/
void speex_resampler_get_ratio(SpeexResamplerState *st,
spx_uint32_t *ratio_num,
spx_uint32_t *ratio_den);
/** Set (change) the conversion quality.
* @param st Resampler state
* @param quality Resampling quality between 0 and 10, where 0 has poor
* quality and 10 has very high quality.
*/
int speex_resampler_set_quality(SpeexResamplerState *st,
int quality);
/** Get the conversion quality.
* @param st Resampler state
* @param quality Resampling quality between 0 and 10, where 0 has poor
* quality and 10 has very high quality.
*/
void speex_resampler_get_quality(SpeexResamplerState *st,
int *quality);
/** Set (change) the input stride.
* @param st Resampler state
* @param stride Input stride
*/
void speex_resampler_set_input_stride(SpeexResamplerState *st,
spx_uint32_t stride);
/** Get the input stride.
* @param st Resampler state
* @param stride Input stride copied
*/
void speex_resampler_get_input_stride(SpeexResamplerState *st,
spx_uint32_t *stride);
/** Set (change) the output stride.
* @param st Resampler state
* @param stride Output stride
*/
void speex_resampler_set_output_stride(SpeexResamplerState *st,
spx_uint32_t stride);
/** Get the output stride.
* @param st Resampler state copied
* @param stride Output stride
*/
void speex_resampler_get_output_stride(SpeexResamplerState *st,
spx_uint32_t *stride);
/** Get the latency introduced by the resampler measured in input samples.
* @param st Resampler state
*/
int speex_resampler_get_input_latency(SpeexResamplerState *st);
/** Get the latency introduced by the resampler measured in output samples.
* @param st Resampler state
*/
int speex_resampler_get_output_latency(SpeexResamplerState *st);
/** Make sure that the first samples to go out of the resamplers don't have
* leading zeros. This is only useful before starting to use a newly created
* resampler. It is recommended to use that when resampling an audio file, as
* it will generate a file with the same length. For real-time processing,
* it is probably easier not to use this call (so that the output duration
* is the same for the first frame).
* @param st Resampler state
*/
int speex_resampler_skip_zeros(SpeexResamplerState *st);
/** Reset a resampler so a new (unrelated) stream can be processed.
* @param st Resampler state
*/
int speex_resampler_reset_mem(SpeexResamplerState *st);
/** Returns the English meaning for an error code
* @param err Error code
* @return English string
*/
const char *speex_resampler_strerror(int err);
#ifdef __cplusplus
}
#endif
#endif

View file

@ -0,0 +1,115 @@
/* Copyright (C) 2002 Jean-Marc Valin */
/**
@file stack_alloc.h
@brief Temporary memory allocation on stack
*/
/*
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
- Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
- Neither the name of the Xiph.org Foundation 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 FOUNDATION 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.
*/
#ifndef STACK_ALLOC_H
#define STACK_ALLOC_H
#ifdef USE_ALLOCA
# ifdef WIN32
# include <malloc.h>
# else
# ifdef HAVE_ALLOCA_H
# include <alloca.h>
# else
# include <stdlib.h>
# endif
# endif
#endif
/**
* @def ALIGN(stack, size)
*
* Aligns the stack to a 'size' boundary
*
* @param stack Stack
* @param size New size boundary
*/
/**
* @def PUSH(stack, size, type)
*
* Allocates 'size' elements of type 'type' on the stack
*
* @param stack Stack
* @param size Number of elements
* @param type Type of element
*/
/**
* @def VARDECL(var)
*
* Declare variable on stack
*
* @param var Variable to declare
*/
/**
* @def ALLOC(var, size, type)
*
* Allocate 'size' elements of 'type' on stack
*
* @param var Name of variable to allocate
* @param size Number of elements
* @param type Type of element
*/
#ifdef ENABLE_VALGRIND
#include <valgrind/memcheck.h>
#define ALIGN(stack, size) ((stack) += ((size) - (long)(stack)) & ((size) - 1))
#define PUSH(stack, size, type) (VALGRIND_MAKE_NOACCESS(stack, 1000),ALIGN((stack),sizeof(type)),VALGRIND_MAKE_WRITABLE(stack, ((size)*sizeof(type))),(stack)+=((size)*sizeof(type)),(type*)((stack)-((size)*sizeof(type))))
#else
#define ALIGN(stack, size) ((stack) += ((size) - (long)(stack)) & ((size) - 1))
#define PUSH(stack, size, type) (ALIGN((stack),sizeof(type)),(stack)+=((size)*sizeof(type)),(type*)((stack)-((size)*sizeof(type))))
#endif
#if defined(VAR_ARRAYS)
#define VARDECL(var)
#define ALLOC(var, size, type) type var[size]
#elif defined(USE_ALLOCA)
#define VARDECL(var) var
#define ALLOC(var, size, type) var = alloca(sizeof(type)*(size))
#else
#define VARDECL(var) var
#define ALLOC(var, size, type) var = PUSH(stack, size, type)
#endif
#endif

View file

@ -179,3 +179,137 @@ TEST(cubeb, duplex_collection_change)
ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb stream"; ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb stream";
cubeb_stream_destroy(stream); cubeb_stream_destroy(stream);
} }
long data_cb_input(cubeb_stream * stream, void * user, const void * inputbuffer, void * outputbuffer, long nframes)
{
if (stream == NULL || inputbuffer == NULL || outputbuffer != NULL) {
return CUBEB_ERROR;
}
return nframes;
}
void state_cb_input(cubeb_stream * stream, void * /*user*/, cubeb_state state)
{
if (stream == NULL)
return;
switch (state) {
case CUBEB_STATE_STARTED:
fprintf(stderr, "stream started\n"); break;
case CUBEB_STATE_STOPPED:
fprintf(stderr, "stream stopped\n"); break;
case CUBEB_STATE_DRAINED:
fprintf(stderr, "stream drained\n"); break;
case CUBEB_STATE_ERROR:
fprintf(stderr, "stream runs into error state\n"); break;
default:
fprintf(stderr, "unknown stream state %d\n", state);
}
return;
}
std::vector<cubeb_devid> get_devices(cubeb * ctx, cubeb_device_type type) {
std::vector<cubeb_devid> devices;
cubeb_device_collection collection;
int r = cubeb_enumerate_devices(ctx, type, &collection);
if (r != CUBEB_OK) {
fprintf(stderr, "Failed to enumerate devices\n");
return devices;
}
for (uint32_t i = 0; i < collection.count; i++) {
if (collection.device[i].state == CUBEB_DEVICE_STATE_ENABLED) {
devices.emplace_back(collection.device[i].devid);
}
}
cubeb_device_collection_destroy(ctx, &collection);
return devices;
}
TEST(cubeb, one_duplex_one_input)
{
cubeb *ctx;
cubeb_stream *duplex_stream;
cubeb_stream_params input_params;
cubeb_stream_params output_params;
int r;
user_state_duplex duplex_stream_state;
uint32_t latency_frames = 0;
r = common_init(&ctx, "Cubeb duplex example");
ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb library";
std::unique_ptr<cubeb, decltype(&cubeb_destroy)>
cleanup_cubeb_at_exit(ctx, cubeb_destroy);
/* This test needs at least two available input devices. */
std::vector<cubeb_devid> input_devices = get_devices(ctx, CUBEB_DEVICE_TYPE_INPUT);
if (input_devices.size() < 2) {
return;
}
/* This test needs at least one available output device. */
std::vector<cubeb_devid> output_devices = get_devices(ctx, CUBEB_DEVICE_TYPE_OUTPUT);
if (output_devices.size() < 1) {
return;
}
cubeb_devid duplex_input = input_devices.front();
cubeb_devid duplex_output = nullptr; // default device
cubeb_devid input_only = input_devices.back();
/* typical use-case: mono voice input, stereo output, low latency. */
input_params.format = STREAM_FORMAT;
input_params.rate = SAMPLE_FREQUENCY;
input_params.channels = INPUT_CHANNELS;
input_params.layout = CUBEB_LAYOUT_UNDEFINED;
input_params.prefs = CUBEB_STREAM_PREF_VOICE;
output_params.format = STREAM_FORMAT;
output_params.rate = SAMPLE_FREQUENCY;
output_params.channels = OUTPUT_CHANNELS;
output_params.layout = OUTPUT_LAYOUT;
output_params.prefs = CUBEB_STREAM_PREF_NONE;
r = cubeb_get_min_latency(ctx, &output_params, &latency_frames);
ASSERT_EQ(r, CUBEB_OK) << "Could not get minimal latency";
r = cubeb_stream_init(ctx, &duplex_stream, "Cubeb duplex",
duplex_input, &input_params, duplex_output, &output_params,
latency_frames, data_cb_duplex, state_cb_duplex, &duplex_stream_state);
ASSERT_EQ(r, CUBEB_OK) << "Error initializing duplex cubeb stream";
std::unique_ptr<cubeb_stream, decltype(&cubeb_stream_destroy)>
cleanup_stream_at_exit(duplex_stream, cubeb_stream_destroy);
r = cubeb_stream_start(duplex_stream);
ASSERT_EQ(r, CUBEB_OK) << "Could not start duplex stream";
delay(500);
cubeb_stream *input_stream;
r = cubeb_stream_init(ctx, &input_stream, "Cubeb input",
input_only, &input_params, NULL, NULL,
latency_frames, data_cb_input, state_cb_input, nullptr);
ASSERT_EQ(r, CUBEB_OK) << "Error initializing input-only cubeb stream";
std::unique_ptr<cubeb_stream, decltype(&cubeb_stream_destroy)>
cleanup_input_stream_at_exit(input_stream, cubeb_stream_destroy);
r = cubeb_stream_start(input_stream);
ASSERT_EQ(r, CUBEB_OK) << "Could not start input stream";
delay(500);
r = cubeb_stream_stop(duplex_stream);
ASSERT_EQ(r, CUBEB_OK) << "Could not stop duplex stream";
r = cubeb_stream_stop(input_stream);
ASSERT_EQ(r, CUBEB_OK) << "Could not stop input stream";
ASSERT_FALSE(duplex_stream_state.invalid_audio_value.load());
}

View file

@ -612,7 +612,7 @@ TEST(cubeb, drain)
r = cubeb_stream_start(stream); r = cubeb_stream_start(stream);
ASSERT_EQ(r, CUBEB_OK); ASSERT_EQ(r, CUBEB_OK);
delay(500); delay(5000);
do_drain = 1; do_drain = 1;
@ -642,65 +642,6 @@ TEST(cubeb, drain)
do_drain = 0; do_drain = 0;
} }
TEST(cubeb, device_reset)
{
int r;
cubeb * ctx;
cubeb_stream * stream;
cubeb_stream_params params;
uint64_t position;
r = common_init(&ctx, "test_sanity");
ASSERT_EQ(r, CUBEB_OK);
ASSERT_NE(ctx, nullptr);
if (strcmp(cubeb_get_backend_id(ctx), "wasapi")) {
// cubeb_stream_reset_default_device is only useful and implemented in the
// WASAPI backend.
return;
}
params.format = STREAM_FORMAT;
params.rate = STREAM_RATE;
params.channels = STREAM_CHANNELS;
params.layout = STREAM_LAYOUT;
params.prefs = CUBEB_STREAM_PREF_NONE;
r = cubeb_stream_init(ctx, &stream, "test", NULL, NULL, NULL, &params, STREAM_LATENCY,
test_data_callback, test_state_callback, &dummy);
ASSERT_EQ(r, CUBEB_OK);
ASSERT_NE(stream, nullptr);
r = cubeb_stream_start(stream);
ASSERT_EQ(r, CUBEB_OK);
uint32_t iterations = 5;
uint64_t previous_position = 0;
while (iterations--) {
r = cubeb_stream_get_position(stream, &position);
ASSERT_EQ(r, CUBEB_OK);
ASSERT_GE(position, previous_position);
previous_position = position;
delay(100);
}
r = cubeb_stream_reset_default_device(stream);
ASSERT_EQ(r, CUBEB_OK);
iterations = 5;
while (iterations--) {
r = cubeb_stream_get_position(stream, &position);
ASSERT_EQ(r, CUBEB_OK);
ASSERT_GE(position, previous_position);
previous_position = position;
delay(100);
}
cubeb_stream_stop(stream);
cubeb_stream_destroy(stream);
cubeb_destroy(ctx);
}
TEST(cubeb, DISABLED_eos_during_prefill) TEST(cubeb, DISABLED_eos_during_prefill)
{ {
// This test needs to be implemented. // This test needs to be implemented.

View file

@ -114,7 +114,7 @@ TEST(cubeb, tone)
cleanup_stream_at_exit(stream, cubeb_stream_destroy); cleanup_stream_at_exit(stream, cubeb_stream_destroy);
cubeb_stream_start(stream); cubeb_stream_start(stream);
delay(500); delay(5000);
cubeb_stream_stop(stream); cubeb_stream_stop(stream);
ASSERT_TRUE(user_data->position.load()); ASSERT_TRUE(user_data->position.load());