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,19 +9,20 @@ 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(USE_SANITIZERS)
if(NOT COMMAND add_sanitizers) if(NOT COMMAND add_sanitizers)
list(APPEND CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake/sanitizers-cmake/cmake") list(APPEND CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake/sanitizers-cmake/cmake")
find_package(Sanitizers) find_package(Sanitizers)
@ -29,13 +30,21 @@ if(NOT COMMAND add_sanitizers)
message(FATAL_ERROR "Could not find sanitizers-cmake: run\n\tgit submodule update --init --recursive\nin base git checkout") message(FATAL_ERROR "Could not find sanitizers-cmake: run\n\tgit submodule update --init --recursive\nin base git checkout")
endif() endif()
endif() endif()
else()
macro(add_sanitizers UNUSED)
endmacro()
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)
if(PKG_CONFIG_FOUND)
pkg_check_modules(speexdsp IMPORTED_TARGET speexdsp)
if(speexdsp_FOUND)
add_library(speex ALIAS PkgConfig::speexdsp)
endif()
endif()
endif()
if(NOT TARGET speex)
add_library(speex STATIC subprojects/speex/resample.c)
set_target_properties(speex PROPERTIES POSITION_INDEPENDENT_CODE TRUE) set_target_properties(speex PROPERTIES POSITION_INDEPENDENT_CODE TRUE)
target_compile_definitions(speex PRIVATE OUTSIDE_SPEEX) target_include_directories(speex INTERFACE subprojects)
target_compile_definitions(speex PRIVATE FLOATING_POINT) target_compile_definitions(speex PUBLIC
target_compile_definitions(speex PRIVATE EXPORT=) OUTSIDE_SPEEX
target_compile_definitions(speex PRIVATE RANDOM_PREFIX=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 {
@ -223,29 +227,31 @@ 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 =
0x01, /**< Request a loopback stream. Should be
specified on the input params and an specified on the input params and an
output device to loopback from should output device to loopback from should
be passed in place of an input device. */ 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 =
0x04, /**< This stream is going to transport voice data.
Depending on the backend and platform, this can Depending on the backend and platform, this can
change the audio input or output devices change the audio input or output devices
selected, as well as the quality of the stream, selected, as well as the quality of the stream,
for example to accomodate bluetooth SCO modes on for example to accomodate bluetooth SCO modes on
bluetooth devices. */ bluetooth devices. */
CUBEB_STREAM_PREF_RAW = 0x08, /**< Windows only. Bypass all signal processing CUBEB_STREAM_PREF_RAW =
0x08, /**< Windows only. Bypass all signal processing
except for always on APO, driver and hardware. */ except for always on APO, driver and hardware. */
CUBEB_STREAM_PREF_PERSIST = 0x10, /**< Request that the volume and mute settings CUBEB_STREAM_PREF_PERSIST = 0x10, /**< Request that the volume and mute
should persist across restarts of the stream settings should persist across restarts
and/or application. May not be honored for of the stream and/or application. This is
all backends and platforms. */ obsolete and ignored by all backends. */
CUBEB_STREAM_PREF_JACK_NO_AUTO_CONNECT = 0x20 /**< Don't automatically try to
CUBEB_STREAM_PREF_JACK_NO_AUTO_CONNECT = 0x20 /**< Don't automatically try to connect connect ports. Only affects
ports. Only affects the jack the jack backend. */
backend. */
} cubeb_stream_prefs; } cubeb_stream_prefs;
/** Stream format initialization parameters. */ /** Stream format initialization parameters. */
@ -254,7 +260,9 @@ typedef struct {
#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
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_prefs prefs; /**< Requested preferences. */
} cubeb_stream_params; } cubeb_stream_params;
@ -276,10 +284,13 @@ typedef enum {
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,8 +306,10 @@ 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_UNPLUGGED, /**< The device is enabled, but nothing is
plugged into it. */
CUBEB_DEVICE_STATE_ENABLED /**< The device is enabled. */ CUBEB_DEVICE_STATE_ENABLED /**< The device is enabled. */
} cubeb_device_state; } cubeb_device_state;
@ -313,7 +326,8 @@ typedef enum {
#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. */
@ -323,11 +337,14 @@ typedef enum {
#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
@ -348,9 +365,13 @@ typedef enum {
* `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
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. */ 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). */
@ -358,7 +379,8 @@ typedef struct {
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
default_format; /**< The default sample format for this device. */
uint32_t max_channels; /**< Channels. */ uint32_t max_channels; /**< Channels. */
uint32_t default_rate; /**< Default/Preferred sample rate. */ uint32_t default_rate; /**< Default/Preferred sample rate. */
uint32_t max_rate; /**< Maximum sample rate supported. */ uint32_t max_rate; /**< Maximum sample rate supported. */
@ -398,18 +420,15 @@ 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, void * output_buffer, long nframes);
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);
/** /**
@ -420,7 +439,8 @@ 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
* cubeb_register_device_collection_changed. */
typedef void (*cubeb_device_collection_changed_callback)(cubeb * context, typedef void (*cubeb_device_collection_changed_callback)(cubeb * context,
void * user_ptr); void * user_ptr);
@ -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
cubeb_init(cubeb ** context, char const * context_name,
char const * backend_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,8 +496,8 @@ 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
@ -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, uint32_t 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);
/** 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,7 +633,8 @@ 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_stream_get_current_device(cubeb_stream * stm,
cubeb_device ** const device); cubeb_device ** const device);
/** Destroy a cubeb_device structure. /** Destroy a cubeb_device structure.
@ -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,23 +654,28 @@ 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_stream_register_device_changed_callback(
cubeb_stream * stream,
cubeb_device_changed_callback device_changed_callback); 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`.
@ -653,7 +683,8 @@ CUBEB_EXPORT int cubeb_enumerate_devices(cubeb * 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_destroy(cubeb * context,
cubeb_device_collection * collection); cubeb_device_collection * collection);
/** Registers a callback which is called when the system detects /** Registers a callback which is called when the system detects
@ -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,7 +714,8 @@ 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_set_log_callback(cubeb_log_level log_level,
cubeb_log_callback log_callback); cubeb_log_callback log_callback);
#if defined(__cplusplus) #if defined(__cplusplus)

View file

@ -22,12 +22,14 @@
*/ */
/* /*
* 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;
@ -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
*/ */
@ -63,14 +66,16 @@ 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;

View file

@ -17,10 +17,12 @@ 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)

View file

@ -29,7 +29,8 @@
/** 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. */
@ -46,7 +47,6 @@
/** 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 */
/*---------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------*/
@ -69,7 +69,6 @@
/* 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,15 +84,18 @@
* 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
* processing. */
#define SL_ANDROID_PERFORMANCE_NONE ((SLuint32)0x00000000) #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
* processing. */
#define SL_ANDROID_PERFORMANCE_LATENCY_EFFECTS ((SLuint32)0x00000002) #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. */

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>
@ -37,8 +37,7 @@ 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,
@ -46,21 +45,16 @@ struct cubeb_ops {
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, int (*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, 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);
void (*stream_destroy)(cubeb_stream * stream); void (*stream_destroy)(cubeb_stream * stream);
int (*stream_start)(cubeb_stream * stream); int (*stream_start)(cubeb_stream * stream);
int (*stream_stop)(cubeb_stream * stream); int (*stream_stop)(cubeb_stream * stream);
int (* stream_reset_default_device)(cubeb_stream * stream);
int (*stream_get_position)(cubeb_stream * stream, uint64_t * position); int (*stream_get_position)(cubeb_stream * stream, uint64_t * position);
int (*stream_get_latency)(cubeb_stream * stream, uint32_t * latency); int (*stream_get_latency)(cubeb_stream * stream, uint32_t * latency);
int (*stream_get_input_latency)(cubeb_stream * stream, uint32_t * latency); int (*stream_get_input_latency)(cubeb_stream * stream, uint32_t * latency);
@ -68,14 +62,13 @@ struct cubeb_ops {
int (*stream_set_name)(cubeb_stream * stream, char const * stream_name); int (*stream_set_name)(cubeb_stream * stream, char const * stream_name);
int (*stream_get_current_device)(cubeb_stream * stream, int (*stream_get_current_device)(cubeb_stream * stream,
cubeb_device ** const device); cubeb_device ** const device);
int (* stream_device_destroy)(cubeb_stream * stream, int (*stream_device_destroy)(cubeb_stream * stream, cubeb_device * device);
cubeb_device * device); int (*stream_register_device_changed_callback)(
int (* stream_register_device_changed_callback)(cubeb_stream * stream, cubeb_stream * stream,
cubeb_device_changed_callback device_changed_callback); cubeb_device_changed_callback device_changed_callback);
int (* register_device_collection_changed)(cubeb * context, int (*register_device_collection_changed)(
cubeb_device_type devtype, cubeb * context, cubeb_device_type devtype,
cubeb_device_collection_changed_callback callback, cubeb_device_collection_changed_callback callback, void * user_ptr);
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,19 +10,14 @@
#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

View file

@ -5,12 +5,12 @@
* 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])))
@ -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,14 +94,18 @@ 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;
} }
} }
@ -96,8 +118,8 @@ validate_stream_params(cubeb_stream_params * input_stream_params,
} }
} }
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,7 +142,8 @@ 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;
@ -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)
@ -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,
@ -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,7 +485,8 @@ 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_stream_get_current_device(cubeb_stream * stream,
cubeb_device ** const device) cubeb_device ** const device)
{ {
if (!stream || !device) { if (!stream || !device) {
@ -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,7 +514,9 @@ 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_stream_register_device_changed_callback(
cubeb_stream * stream,
cubeb_device_changed_callback device_changed_callback) cubeb_device_changed_callback device_changed_callback)
{ {
if (!stream) { if (!stream) {
@ -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,8 +541,8 @@ 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;
@ -599,19 +617,16 @@ 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;
@ -633,7 +648,8 @@ int cubeb_enumerate_devices(cubeb * context,
return rv; return rv;
} }
int cubeb_device_collection_destroy(cubeb * context, int
cubeb_device_collection_destroy(cubeb * context,
cubeb_device_collection * collection) cubeb_device_collection * collection)
{ {
int r; int r;
@ -656,22 +672,25 @@ 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_set_log_callback(cubeb_log_level log_level,
cubeb_log_callback log_callback) 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) {
@ -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,21 +8,21 @@
#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) \
@ -57,7 +57,7 @@
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,7 +336,8 @@ 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;
} }
@ -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;
@ -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;
} }
@ -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;
} }
@ -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,7 +837,8 @@ alsa_init(cubeb ** context, char const * context_name)
} }
} }
#define LOAD(x) { \ #define LOAD(x) \
{ \
cubeb_##x = dlsym(libasound, #x); \ cubeb_##x = dlsym(libasound, #x); \
if (!cubeb_##x) { \ if (!cubeb_##x) { \
dlclose(libasound); \ dlclose(libasound); \
@ -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;
@ -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);
@ -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;
@ -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
@ -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;
} }
@ -1441,7 +1471,6 @@ static struct cubeb_ops const alsa_ops = {
.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,
@ -1450,5 +1479,4 @@ static struct cubeb_ops const alsa_ops = {
.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,7 +24,8 @@ 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));
@ -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;

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
@ -46,17 +47,24 @@
} 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,
uint32_t rate);
void * (*ctor)(void * instance, int, unsigned int, int, int, int,
unsigned int, void (*)(int, void *, void *), void *, int, int);
void * (*dtor)(void * instance); void * (*dtor)(void * instance);
void (*start)(void * instance); void (*start)(void * instance);
void (*pause)(void * instance); void (*pause)(void * instance);
@ -101,7 +109,8 @@ audiotrack_refill(int event, void* user, void* info)
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");
@ -182,33 +197,44 @@ 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 || c->get_min_frame_count_gingerbread) &&
c->get_min_frame_count_gingerbread) &&
c->start && c->pause && c->get_position && c->set_marker_position)) { 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);
@ -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.");
@ -430,7 +461,6 @@ static struct cubeb_ops const audiotrack_ops = {
.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,
@ -439,5 +469,4 @@ static struct cubeb_ops const audiotrack_ops = {
.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,21 +11,25 @@
#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>
#ifdef DISABLE_LIBJACK_DLOPEN
#define WRAP(x) x
#else
#define WRAP(x) (*api_##x)
#define JACK_API_VISIT(X) \ #define JACK_API_VISIT(X) \
X(jack_activate) \ X(jack_activate) \
X(jack_client_close) \ X(jack_client_close) \
@ -49,6 +53,8 @@
#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"
@ -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,46 +87,72 @@ 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);
static void
cbjack_interleave_capture(cubeb_stream * stream, float ** in,
jack_nframes_t nframes, bool format_mismatch);
static void
cbjack_deinterleave_playback_refill_s16ne(cubeb_stream * stream,
short ** bufs_in, float ** bufs_out,
jack_nframes_t nframes);
static void
cbjack_deinterleave_playback_refill_float(cubeb_stream * stream,
float ** bufs_in, float ** bufs_out,
jack_nframes_t nframes);
static int
cbjack_stream_device_destroy(cubeb_stream * stream, cubeb_device * device);
static int
cbjack_stream_get_current_device(cubeb_stream * stm,
cubeb_device ** const device);
static int
cbjack_enumerate_devices(cubeb * context, cubeb_device_type type,
cubeb_device_collection * collection); cubeb_device_collection * collection);
static int cbjack_device_collection_destroy(cubeb * context, static int
cbjack_device_collection_destroy(cubeb * context,
cubeb_device_collection * collection); cubeb_device_collection * collection);
static int cbjack_stream_init(cubeb * context, cubeb_stream ** stream, char const * stream_name, static int
cubeb_devid input_device, cbjack_stream_init(cubeb * context, cubeb_stream ** stream,
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); static void
static void cbjack_stream_destroy(cubeb_stream * stream); cbjack_stream_destroy(cubeb_stream * stream);
static int cbjack_stream_start(cubeb_stream * stream); static int
static int cbjack_stream_stop(cubeb_stream * stream); cbjack_stream_start(cubeb_stream * stream);
static int cbjack_stream_get_position(cubeb_stream * stream, uint64_t * position); static int
static int cbjack_stream_set_volume(cubeb_stream * stm, float volume); 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,
@ -129,7 +167,6 @@ static struct cubeb_ops const cbjack_ops = {
.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,
@ -138,8 +175,7 @@ static struct cubeb_ops const cbjack_ops = {
.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. */
@ -205,6 +241,7 @@ 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);
@ -235,45 +272,48 @@ load_jack_lib(cubeb * context)
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;
@ -332,7 +375,8 @@ 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;
@ -373,12 +417,14 @@ 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
@ -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 =
stm->context->in_resampled_interleaved_buffer_s16ne;
float * in_float = stm->context->in_resampled_interleaved_buffer_float; 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
@ -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
: NULL,
needed_frames); 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
: NULL,
needed_frames); 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));
} }
} }
@ -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,21 +965,20 @@ 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) {
if (!input_stream_params->prefs & CUBEB_STREAM_PREF_JACK_NO_AUTO_CONNECT) {
if (cbjack_connect_ports(stm) != CUBEB_OK) {
pthread_mutex_unlock(&stm->mutex); pthread_mutex_unlock(&stm->mutex);
cbjack_stream_destroy(stm); cbjack_stream_destroy(stm);
return CUBEB_ERROR; return CUBEB_ERROR;
} }
} }
}
}
*stream = stm; *stream = stm;
@ -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);

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)
@ -121,8 +122,7 @@ kai_callback(PVOID cbdata, PVOID buffer, ULONG len)
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);
@ -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,
@ -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;
} }
@ -358,7 +358,6 @@ static struct cubeb_ops const kai_ops = {
/*.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,
@ -367,5 +366,4 @@ static struct cubeb_ops const kai_ops = {
/*.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

@ -31,13 +31,9 @@ const size_t CUBEB_LOG_MESSAGE_QUEUE_DEPTH = 40;
* 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,13 +44,16 @@ 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, ...) \
do { \
if (g_cubeb_log_callback && level <= g_cubeb_log_level) { \ if (g_cubeb_log_callback && level <= g_cubeb_log_level) { \
g_cubeb_log_callback("%s:%d: " fmt "\n", __FILENAME__, __LINE__, ##__VA_ARGS__); \ g_cubeb_log_callback("%s:%d: " fmt "\n", __FILENAME__, __LINE__, \
##__VA_ARGS__); \
} \ } \
} while (0) } while (0)
/* Asynchronous verbose logging, to log in real-time callbacks. */ /* Asynchronous verbose logging, to log in real-time callbacks. */
/* Should not be used on android due to the use of global/static variables. */
#define ALOGV(fmt, ...) \ #define ALOGV(fmt, ...) \
do { \ do { \
cubeb_async_log(fmt, ##__VA_ARGS__); \ cubeb_async_log(fmt, ##__VA_ARGS__); \

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,9 +19,6 @@
#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]))
@ -66,14 +66,17 @@ 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);
@ -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)) {
@ -166,15 +165,21 @@ struct MixerContext {
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
int32_t _matrix32[CHANNELS_MAX][CHANNELS_MAX] = {
{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 _clipping = false; ///< Set to true if clipping detection is required
bool _valid = false; ///< Set to true if context is valid. 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;
@ -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) {
@ -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) {
@ -400,20 +405,13 @@ int MixerContext::init()
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;
@ -424,18 +422,11 @@ 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,13 +435,12 @@ 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++) {
@ -463,32 +453,21 @@ static int rematrix(const MixerContext * s, TYPE * aOut, const TYPE * aIn,
break; break;
case 1: { case 1: {
int in_i = s->_matrix_ch[out_i][1]; int in_i = s->_matrix_ch[out_i][1];
copy(out, copy(out, s->_out_ch_count, aIn + in_i, s->_in_ch_count,
s->_out_ch_count, matrix_coeff[out_i][in_i], aF, frames);
aIn + in_i,
s->_in_ch_count,
matrix_coeff[out_i][in_i],
aF,
frames);
} break; } break;
case 2: case 2:
sum2(out, sum2(out, s->_out_ch_count, aIn + s->_matrix_ch[out_i][1],
s->_out_ch_count, aIn + s->_matrix_ch[out_i][2], s->_in_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][1]],
matrix_coeff[out_i][s->_matrix_ch[out_i][2]], matrix_coeff[out_i][s->_matrix_ch[out_i][2]], aF, frames);
aF,
frames);
break; break;
default: default:
for (uint32_t i = 0; i < frames; i++) { for (uint32_t i = 0; i < frames; i++) {
TYPE_COEFF v = 0; TYPE_COEFF v = 0;
for (uint32_t j = 0; j < s->_matrix_ch[out_i][0]; j++) { for (uint32_t j = 0; j < s->_matrix_ch[out_i][0]; j++) {
uint32_t in_i = s->_matrix_ch[out_i][1 + j]; uint32_t in_i = s->_matrix_ch[out_i][1 + j];
v += v += *(aIn + in_i + i * s->_in_ch_count) * matrix_coeff[out_i][in_i];
*(aIn + in_i + i * s->_in_ch_count) * matrix_coeff[out_i][in_i];
} }
out[i * s->_out_ch_count] = aF(v); out[i * s->_out_ch_count] = aF(v);
} }
@ -498,20 +477,16 @@ static int rematrix(const MixerContext * s, TYPE * aOut, const TYPE * aIn,
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;
@ -571,28 +543,22 @@ 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, return rematrix(&_context, static_cast<float *>(output_buffer),
static_cast<float*>(output_buffer),
static_cast<const float *>(input_buffer), static_cast<const float *>(input_buffer),
_context._matrix_flt, _context._matrix_flt, f, frames);
f,
frames);
} }
case CUBEB_SAMPLE_S16NE: case CUBEB_SAMPLE_S16NE:
if (_context._clipping) { if (_context._clipping) {
@ -604,20 +570,14 @@ struct cubeb_mixer
} }
return y; return y;
}; };
return rematrix(&_context, return rematrix(&_context, static_cast<int16_t *>(output_buffer),
static_cast<int16_t*>(output_buffer),
static_cast<const int16_t *>(input_buffer), static_cast<const int16_t *>(input_buffer),
_context._matrix32, _context._matrix32, f, frames);
f,
frames);
} else { } else {
auto f = [](int x) { return (x + 16384) >> 15; }; auto f = [](int x) { return (x + 16384) >> 15; };
return rematrix(&_context, return rematrix(&_context, static_cast<int16_t *>(output_buffer),
static_cast<int16_t*>(output_buffer),
static_cast<const int16_t *>(input_buffer), static_cast<const int16_t *>(input_buffer),
_context._matrix32, _context._matrix32, f, frames);
f,
frames);
} }
break; break;
default: default:
@ -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,
void * output_buffer,
size_t output_buffer_size) 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 cubeb_mixer_destroy(cubeb_mixer * mixer); void
int cubeb_mixer_mix(cubeb_mixer * mixer, cubeb_mixer_destroy(cubeb_mixer * mixer);
size_t frames, int
const void * input_buffer, cubeb_mixer_mix(cubeb_mixer * mixer, size_t frames, const void * input_buffer,
size_t input_buffer_size, size_t input_buffer_size, void * output_buffer,
void * output_buffer,
size_t output_buffer_size); 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)
} }

View file

@ -5,28 +5,29 @@
* accompanying file LICENSE for details. * accompanying file LICENSE for details.
*/ */
#undef NDEBUG #undef NDEBUG
#include <SLES/OpenSLES.h>
#include <assert.h> #include <assert.h>
#include <dlfcn.h> #include <dlfcn.h>
#include <stdlib.h>
#include <pthread.h>
#include <errno.h> #include <errno.h>
#include <SLES/OpenSLES.h>
#include <math.h> #include <math.h>
#include <pthread.h>
#include <stdlib.h>
#include <time.h> #include <time.h>
#if defined(__ANDROID__) #if defined(__ANDROID__)
#include <dlfcn.h>
#include <sys/system_properties.h>
#include "android/sles_definitions.h" #include "android/sles_definitions.h"
#include <SLES/OpenSLES_Android.h> #include <SLES/OpenSLES_Android.h>
#include <android/log.h>
#include <android/api-level.h> #include <android/api-level.h>
#include <android/log.h>
#include <dlfcn.h>
#include <sys/system_properties.h>
#endif #endif
#include "cubeb/cubeb.h"
#include "cubeb-internal.h"
#include "cubeb_resampler.h"
#include "cubeb-sles.h"
#include "cubeb_array_queue.h"
#include "android/cubeb-output-latency.h" #include "android/cubeb-output-latency.h"
#include "cubeb-internal.h"
#include "cubeb-sles.h"
#include "cubeb/cubeb.h"
#include "cubeb_android.h"
#include "cubeb_array_queue.h"
#include "cubeb_resampler.h"
#if defined(__ANDROID__) #if defined(__ANDROID__)
#ifdef LOG #ifdef LOG
@ -34,22 +35,30 @@
#endif #endif
//#define LOGGING_ENABLED //#define LOGGING_ENABLED
#ifdef LOGGING_ENABLED #ifdef LOGGING_ENABLED
#define LOG(args...) __android_log_print(ANDROID_LOG_INFO, "Cubeb_OpenSL" , ## args) #define LOG(args...) \
__android_log_print(ANDROID_LOG_INFO, "Cubeb_OpenSL", ##args)
#else #else
#define LOG(...) #define LOG(...)
#endif #endif
//#define TIMESTAMP_ENABLED //#define TIMESTAMP_ENABLED
#ifdef TIMESTAMP_ENABLED #ifdef TIMESTAMP_ENABLED
#define FILENAME (strrchr(__FILE__, '/') ? strrchr(__FILE__, '/') + 1 : __FILE__) #define FILENAME \
#define LOG_TS(args...) __android_log_print(ANDROID_LOG_INFO, "Cubeb_OpenSL ES: Timestamp(usec)" , ## args) (strrchr(__FILE__, '/') ? strrchr(__FILE__, '/') + 1 : __FILE__)
#define TIMESTAMP(msg) do { \ #define LOG_TS(args...) \
__android_log_print(ANDROID_LOG_INFO, "Cubeb_OpenSL ES: Timestamp(usec)", \
##args)
#define TIMESTAMP(msg) \
do { \
struct timeval timestamp; \ struct timeval timestamp; \
int ts_ret = gettimeofday(&timestamp, NULL); \ int ts_ret = gettimeofday(&timestamp, NULL); \
if (ts_ret == 0) { \ if (ts_ret == 0) { \
LOG_TS("%lld: %s (%s %s:%d)", timestamp.tv_sec * 1000000LL + timestamp.tv_usec, msg, __FUNCTION__, FILENAME, __LINE__);\ LOG_TS("%lld: %s (%s %s:%d)", \
timestamp.tv_sec * 1000000LL + timestamp.tv_usec, msg, \
__FUNCTION__, FILENAME, __LINE__); \
} else { \ } else { \
LOG_TS("Error: %s (%s %s:%d) - %s", msg, __FUNCTION__, FILENAME, __LINE__);\ LOG_TS("Error: %s (%s %s:%d) - %s", msg, __FUNCTION__, FILENAME, \
__LINE__); \
} \ } \
} while (0) } while (0)
#else #else
@ -65,11 +74,6 @@
#define DEFAULT_SAMPLE_RATE 48000 #define DEFAULT_SAMPLE_RATE 48000
#define DEFAULT_NUM_OF_FRAMES 480 #define DEFAULT_NUM_OF_FRAMES 480
// 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
#define POWERSAVE_LATENCY_FRAMES_THRESHOLD 4000
static struct cubeb_ops const opensl_ops; static struct cubeb_ops const opensl_ops;
@ -173,15 +177,18 @@ struct cubeb_stream {
}; };
/* Forward declaration. */ /* Forward declaration. */
static int opensl_stop_player(cubeb_stream * stm); static int
static int opensl_stop_recorder(cubeb_stream * stm); opensl_stop_player(cubeb_stream * stm);
static int
opensl_stop_recorder(cubeb_stream * stm);
static int static int
opensl_get_draining(cubeb_stream * stm) opensl_get_draining(cubeb_stream * stm)
{ {
#ifdef DEBUG #ifdef DEBUG
int r = pthread_mutex_trylock(&stm->mutex); int r = pthread_mutex_trylock(&stm->mutex);
assert((r == EDEADLK || r == EBUSY) && "get_draining: mutex should be locked but it's not."); assert((r == EDEADLK || r == EBUSY) &&
"get_draining: mutex should be locked but it's not.");
#endif #endif
return stm->draining; return stm->draining;
} }
@ -192,7 +199,8 @@ opensl_set_draining(cubeb_stream * stm, int value)
#ifdef DEBUG #ifdef DEBUG
int r = pthread_mutex_trylock(&stm->mutex); int r = pthread_mutex_trylock(&stm->mutex);
LOG("set draining try r = %d", r); LOG("set draining try r = %d", r);
assert((r == EDEADLK || r == EBUSY) && "set_draining: mutex should be locked but it's not."); assert((r == EDEADLK || r == EBUSY) &&
"set_draining: mutex should be locked but it's not.");
#endif #endif
assert(value == 0 || value == 1); assert(value == 0 || value == 1);
stm->draining = value; stm->draining = value;
@ -226,7 +234,8 @@ opensl_get_shutdown(cubeb_stream * stm)
{ {
#ifdef DEBUG #ifdef DEBUG
int r = pthread_mutex_trylock(&stm->mutex); int r = pthread_mutex_trylock(&stm->mutex);
assert((r == EDEADLK || r == EBUSY) && "get_shutdown: mutex should be locked but it's not."); assert((r == EDEADLK || r == EBUSY) &&
"get_shutdown: mutex should be locked but it's not.");
#endif #endif
return stm->shutdown; return stm->shutdown;
} }
@ -237,7 +246,8 @@ opensl_set_shutdown(cubeb_stream * stm, uint32_t value)
#ifdef DEBUG #ifdef DEBUG
int r = pthread_mutex_trylock(&stm->mutex); int r = pthread_mutex_trylock(&stm->mutex);
LOG("set shutdown try r = %d", r); LOG("set shutdown try r = %d", r);
assert((r == EDEADLK || r == EBUSY) && "set_shutdown: mutex should be locked but it's not."); assert((r == EDEADLK || r == EBUSY) &&
"set_shutdown: mutex should be locked but it's not.");
#endif #endif
assert(value == 0 || value == 1); assert(value == 0 || value == 1);
stm->shutdown = value; stm->shutdown = value;
@ -308,9 +318,8 @@ bufferqueue_callback(SLBufferQueueItf caller, void * user_ptr)
r = pthread_mutex_unlock(&stm->mutex); r = pthread_mutex_unlock(&stm->mutex);
assert(r == 0); assert(r == 0);
if (!draining && !shutdown) { if (!draining && !shutdown) {
written = cubeb_resampler_fill(stm->resampler, written = cubeb_resampler_fill(stm->resampler, NULL, NULL, buf,
NULL, NULL, stm->queuebuf_len / stm->framesize);
buf, stm->queuebuf_len / stm->framesize);
LOG("bufferqueue_callback: resampler fill returned %ld frames", written); LOG("bufferqueue_callback: resampler fill returned %ld frames", written);
if (written < 0 || written * stm->framesize > stm->queuebuf_len) { if (written < 0 || written * stm->framesize > stm->queuebuf_len) {
r = pthread_mutex_lock(&stm->mutex); r = pthread_mutex_lock(&stm->mutex);
@ -327,7 +336,8 @@ bufferqueue_callback(SLBufferQueueItf caller, void * user_ptr)
// Keep sending silent data even in draining mode to prevent the audio // Keep sending silent data even in draining mode to prevent the audio
// back-end from being stopped automatically by OpenSL/ES. // back-end from being stopped automatically by OpenSL/ES.
assert(stm->queuebuf_len >= written * stm->framesize); assert(stm->queuebuf_len >= written * stm->framesize);
memset(buf + written * stm->framesize, 0, stm->queuebuf_len - written * stm->framesize); memset(buf + written * stm->framesize, 0,
stm->queuebuf_len - written * stm->framesize);
res = (*stm->bufq)->Enqueue(stm->bufq, buf, stm->queuebuf_len); res = (*stm->bufq)->Enqueue(stm->bufq, buf, stm->queuebuf_len);
assert(res == SL_RESULT_SUCCESS); assert(res == SL_RESULT_SUCCESS);
stm->queuebuf_idx = (stm->queuebuf_idx + 1) % stm->queuebuf_capacity; stm->queuebuf_idx = (stm->queuebuf_idx + 1) % stm->queuebuf_capacity;
@ -342,7 +352,8 @@ bufferqueue_callback(SLBufferQueueItf caller, void * user_ptr)
LOG("bufferqueue_callback draining"); LOG("bufferqueue_callback draining");
r = pthread_mutex_lock(&stm->mutex); r = pthread_mutex_lock(&stm->mutex);
assert(r == 0); assert(r == 0);
int64_t written_duration = INT64_C(1000) * stm->written * stm->framesize / stm->bytespersec; int64_t written_duration =
INT64_C(1000) * stm->written * stm->framesize / stm->bytespersec;
opensl_set_draining(stm, 1); opensl_set_draining(stm, 1);
r = pthread_mutex_unlock(&stm->mutex); r = pthread_mutex_unlock(&stm->mutex);
assert(r == 0); assert(r == 0);
@ -354,7 +365,8 @@ bufferqueue_callback(SLBufferQueueItf caller, void * user_ptr)
} else { } else {
// Use SL_PLAYEVENT_HEADATMARKER event from slPlayCallback of SLPlayItf // Use SL_PLAYEVENT_HEADATMARKER event from slPlayCallback of SLPlayItf
// to make sure all the data has been processed. // to make sure all the data has been processed.
(*stm->play)->SetMarkerPosition(stm->play, (SLmillisecond)written_duration); (*stm->play)
->SetMarkerPosition(stm->play, (SLmillisecond)written_duration);
} }
return; return;
} }
@ -372,13 +384,15 @@ opensl_enqueue_recorder(cubeb_stream * stm, void ** last_filled_buffer)
// This is the first enqueue // This is the first enqueue
current_index = 0; current_index = 0;
} else { } else {
// The current index hold the last filled buffer get it before advance index. // The current index hold the last filled buffer get it before advance
// index.
last_buffer = stm->input_buffer_array[current_index]; last_buffer = stm->input_buffer_array[current_index];
// Advance to get next available buffer // Advance to get next available buffer
current_index = (current_index + 1) % stm->input_array_capacity; current_index = (current_index + 1) % stm->input_array_capacity;
} }
// enqueue next empty buffer to be filled by the recorder // enqueue next empty buffer to be filled by the recorder
SLresult res = (*stm->recorderBufferQueueItf)->Enqueue(stm->recorderBufferQueueItf, SLresult res = (*stm->recorderBufferQueueItf)
->Enqueue(stm->recorderBufferQueueItf,
stm->input_buffer_array[current_index], stm->input_buffer_array[current_index],
stm->input_buffer_length); stm->input_buffer_length);
if (res != SL_RESULT_SUCCESS) { if (res != SL_RESULT_SUCCESS) {
@ -394,7 +408,8 @@ opensl_enqueue_recorder(cubeb_stream * stm, void ** last_filled_buffer)
} }
// input data callback // input data callback
void recorder_callback(SLAndroidSimpleBufferQueueItf bq, void * context) void
recorder_callback(SLAndroidSimpleBufferQueueItf bq, void * context)
{ {
assert(context); assert(context);
cubeb_stream * stm = context; cubeb_stream * stm = context;
@ -424,11 +439,8 @@ void recorder_callback(SLAndroidSimpleBufferQueueItf bq, void * context)
assert(input_buffer); assert(input_buffer);
// Fill resampler with last input // Fill resampler with last input
long input_frame_count = stm->input_buffer_length / stm->input_frame_size; long input_frame_count = stm->input_buffer_length / stm->input_frame_size;
long got = cubeb_resampler_fill(stm->resampler, long got = cubeb_resampler_fill(stm->resampler, input_buffer,
input_buffer, &input_frame_count, NULL, 0);
&input_frame_count,
NULL,
0);
// Error case // Error case
if (got < 0 || got > input_frame_count) { if (got < 0 || got > input_frame_count) {
r = pthread_mutex_lock(&stm->mutex); r = pthread_mutex_lock(&stm->mutex);
@ -450,13 +462,16 @@ void recorder_callback(SLAndroidSimpleBufferQueueItf bq, void * context)
opensl_set_draining(stm, 1); opensl_set_draining(stm, 1);
r = pthread_mutex_unlock(&stm->mutex); r = pthread_mutex_unlock(&stm->mutex);
assert(r == 0); assert(r == 0);
int64_t duration = INT64_C(1000) * stm->input_total_frames / stm->input_device_rate; int64_t duration =
(*stm->recorderItf)->SetMarkerPosition(stm->recorderItf, (SLmillisecond)duration); INT64_C(1000) * stm->input_total_frames / stm->input_device_rate;
(*stm->recorderItf)
->SetMarkerPosition(stm->recorderItf, (SLmillisecond)duration);
return; return;
} }
} }
void recorder_fullduplex_callback(SLAndroidSimpleBufferQueueItf bq, void * context) void
recorder_fullduplex_callback(SLAndroidSimpleBufferQueueItf bq, void * context)
{ {
assert(context); assert(context);
cubeb_stream * stm = context; cubeb_stream * stm = context;
@ -529,9 +544,7 @@ player_fullduplex_callback(SLBufferQueueItf caller, void * user_ptr)
memset(output_buffer, 0, stm->queuebuf_len); memset(output_buffer, 0, stm->queuebuf_len);
// Enqueue data in player buffer queue // Enqueue data in player buffer queue
res = (*stm->bufq)->Enqueue(stm->bufq, res = (*stm->bufq)->Enqueue(stm->bufq, output_buffer, stm->queuebuf_len);
output_buffer,
stm->queuebuf_len);
assert(res == SL_RESULT_SUCCESS); assert(res == SL_RESULT_SUCCESS);
return; return;
} }
@ -547,14 +560,12 @@ player_fullduplex_callback(SLBufferQueueItf caller, void * user_ptr)
long written = 0; long written = 0;
// Trigger user callback through resampler // Trigger user callback through resampler
written = cubeb_resampler_fill(stm->resampler, written =
input_buffer, cubeb_resampler_fill(stm->resampler, input_buffer, &input_frame_count,
&input_frame_count, output_buffer, frames_needed);
output_buffer,
frames_needed);
LOG("Fill: written %ld, frames_needed %ld, input array size %zu", LOG("Fill: written %ld, frames_needed %ld, input array size %zu", written,
written, frames_needed, array_queue_get_size(stm->input_queue)); frames_needed, array_queue_get_size(stm->input_queue));
if (written < 0 || written > frames_needed) { if (written < 0 || written > frames_needed) {
// Error case // Error case
@ -569,9 +580,7 @@ player_fullduplex_callback(SLBufferQueueItf caller, void * user_ptr)
memset(output_buffer, 0, stm->queuebuf_len); memset(output_buffer, 0, stm->queuebuf_len);
// Enqueue data in player buffer queue // Enqueue data in player buffer queue
res = (*stm->bufq)->Enqueue(stm->bufq, res = (*stm->bufq)->Enqueue(stm->bufq, output_buffer, stm->queuebuf_len);
output_buffer,
stm->queuebuf_len);
assert(res == SL_RESULT_SUCCESS); assert(res == SL_RESULT_SUCCESS);
return; return;
} }
@ -586,7 +595,8 @@ player_fullduplex_callback(SLBufferQueueItf caller, void * user_ptr)
if (written < frames_needed) { if (written < frames_needed) {
r = pthread_mutex_lock(&stm->mutex); r = pthread_mutex_lock(&stm->mutex);
assert(r == 0); assert(r == 0);
int64_t written_duration = INT64_C(1000) * stm->written * stm->framesize / stm->bytespersec; int64_t written_duration =
INT64_C(1000) * stm->written * stm->framesize / stm->bytespersec;
opensl_set_draining(stm, 1); opensl_set_draining(stm, 1);
r = pthread_mutex_unlock(&stm->mutex); r = pthread_mutex_unlock(&stm->mutex);
assert(r == 0); assert(r == 0);
@ -602,14 +612,13 @@ player_fullduplex_callback(SLBufferQueueItf caller, void * user_ptr)
stm->queuebuf_len - written * stm->framesize); stm->queuebuf_len - written * stm->framesize);
// Enqueue data in player buffer queue // Enqueue data in player buffer queue
res = (*stm->bufq)->Enqueue(stm->bufq, res = (*stm->bufq)->Enqueue(stm->bufq, output_buffer, stm->queuebuf_len);
output_buffer,
stm->queuebuf_len);
assert(res == SL_RESULT_SUCCESS); assert(res == SL_RESULT_SUCCESS);
TIMESTAMP("EXIT"); TIMESTAMP("EXIT");
} }
static void opensl_destroy(cubeb * ctx); static void
opensl_destroy(cubeb * ctx);
#if defined(__ANDROID__) #if defined(__ANDROID__)
#if (__ANDROID_API__ >= ANDROID_VERSION_LOLLIPOP) #if (__ANDROID_API__ >= ANDROID_VERSION_LOLLIPOP)
@ -623,8 +632,8 @@ wrap_system_property_get(const char* name, char* value)
LOG("Failed to open libc.so"); LOG("Failed to open libc.so");
return -1; return -1;
} }
system_property_get* func = (system_property_get*) system_property_get * func =
dlsym(libc, "__system_property_get"); (system_property_get *)dlsym(libc, "__system_property_get");
int ret = -1; int ret = -1;
if (func) { if (func) {
ret = func(name, value); ret = func(name, value);
@ -664,7 +673,8 @@ opensl_init(cubeb ** context, char const * context_name)
#if defined(__ANDROID__) #if defined(__ANDROID__)
int android_version = get_android_version(); int android_version = get_android_version();
if (android_version > 0 && android_version <= ANDROID_VERSION_GINGERBREAD_MR1) { if (android_version > 0 &&
android_version <= ANDROID_VERSION_GINGERBREAD_MR1) {
// Don't even attempt to run on Gingerbread and lower // Don't even attempt to run on Gingerbread and lower
return CUBEB_ERROR; return CUBEB_ERROR;
} }
@ -683,35 +693,34 @@ opensl_init(cubeb ** context, char const * context_name)
return CUBEB_ERROR; return CUBEB_ERROR;
} }
typedef SLresult (*slCreateEngine_t)(SLObjectItf *, typedef SLresult (*slCreateEngine_t)(
SLuint32, SLObjectItf *, SLuint32, const SLEngineOption *, SLuint32,
const SLEngineOption *, const SLInterfaceID *, const SLboolean *);
SLuint32,
const SLInterfaceID *,
const SLboolean *);
slCreateEngine_t f_slCreateEngine = slCreateEngine_t f_slCreateEngine =
(slCreateEngine_t)dlsym(ctx->lib, "slCreateEngine"); (slCreateEngine_t)dlsym(ctx->lib, "slCreateEngine");
SLInterfaceID SL_IID_ENGINE = *(SLInterfaceID *)dlsym(ctx->lib, "SL_IID_ENGINE"); SLInterfaceID SL_IID_ENGINE =
SLInterfaceID SL_IID_OUTPUTMIX = *(SLInterfaceID *)dlsym(ctx->lib, "SL_IID_OUTPUTMIX"); *(SLInterfaceID *)dlsym(ctx->lib, "SL_IID_ENGINE");
SLInterfaceID SL_IID_OUTPUTMIX =
*(SLInterfaceID *)dlsym(ctx->lib, "SL_IID_OUTPUTMIX");
ctx->SL_IID_VOLUME = *(SLInterfaceID *)dlsym(ctx->lib, "SL_IID_VOLUME"); ctx->SL_IID_VOLUME = *(SLInterfaceID *)dlsym(ctx->lib, "SL_IID_VOLUME");
ctx->SL_IID_BUFFERQUEUE = *(SLInterfaceID *)dlsym(ctx->lib, "SL_IID_BUFFERQUEUE"); ctx->SL_IID_BUFFERQUEUE =
*(SLInterfaceID *)dlsym(ctx->lib, "SL_IID_BUFFERQUEUE");
#if defined(__ANDROID__) #if defined(__ANDROID__)
ctx->SL_IID_ANDROIDCONFIGURATION = *(SLInterfaceID *)dlsym(ctx->lib, "SL_IID_ANDROIDCONFIGURATION"); ctx->SL_IID_ANDROIDCONFIGURATION =
ctx->SL_IID_ANDROIDSIMPLEBUFFERQUEUE = *(SLInterfaceID *)dlsym(ctx->lib, "SL_IID_ANDROIDSIMPLEBUFFERQUEUE"); *(SLInterfaceID *)dlsym(ctx->lib, "SL_IID_ANDROIDCONFIGURATION");
ctx->SL_IID_ANDROIDSIMPLEBUFFERQUEUE =
*(SLInterfaceID *)dlsym(ctx->lib, "SL_IID_ANDROIDSIMPLEBUFFERQUEUE");
#endif #endif
ctx->SL_IID_PLAY = *(SLInterfaceID *)dlsym(ctx->lib, "SL_IID_PLAY"); ctx->SL_IID_PLAY = *(SLInterfaceID *)dlsym(ctx->lib, "SL_IID_PLAY");
ctx->SL_IID_RECORD = *(SLInterfaceID *)dlsym(ctx->lib, "SL_IID_RECORD"); ctx->SL_IID_RECORD = *(SLInterfaceID *)dlsym(ctx->lib, "SL_IID_RECORD");
if (!f_slCreateEngine || if (!f_slCreateEngine || !SL_IID_ENGINE || !SL_IID_OUTPUTMIX ||
!SL_IID_ENGINE ||
!SL_IID_OUTPUTMIX ||
!ctx->SL_IID_BUFFERQUEUE || !ctx->SL_IID_BUFFERQUEUE ||
#if defined(__ANDROID__) #if defined(__ANDROID__)
!ctx->SL_IID_ANDROIDCONFIGURATION || !ctx->SL_IID_ANDROIDCONFIGURATION ||
!ctx->SL_IID_ANDROIDSIMPLEBUFFERQUEUE || !ctx->SL_IID_ANDROIDSIMPLEBUFFERQUEUE ||
#endif #endif
!ctx->SL_IID_PLAY || !ctx->SL_IID_PLAY || !ctx->SL_IID_RECORD) {
!ctx->SL_IID_RECORD) {
opensl_destroy(ctx); opensl_destroy(ctx);
return CUBEB_ERROR; return CUBEB_ERROR;
} }
@ -740,7 +749,8 @@ opensl_init(cubeb ** context, char const * context_name)
const SLInterfaceID idsom[] = {SL_IID_OUTPUTMIX}; const SLInterfaceID idsom[] = {SL_IID_OUTPUTMIX};
const SLboolean reqom[] = {SL_BOOLEAN_TRUE}; const SLboolean reqom[] = {SL_BOOLEAN_TRUE};
res = (*ctx->eng)->CreateOutputMix(ctx->eng, &ctx->outmixObj, 1, idsom, reqom); res =
(*ctx->eng)->CreateOutputMix(ctx->eng, &ctx->outmixObj, 1, idsom, reqom);
if (res != SL_RESULT_SUCCESS) { if (res != SL_RESULT_SUCCESS) {
opensl_destroy(ctx); opensl_destroy(ctx);
return CUBEB_ERROR; return CUBEB_ERROR;
@ -752,9 +762,11 @@ opensl_init(cubeb ** context, char const * context_name)
return CUBEB_ERROR; return CUBEB_ERROR;
} }
ctx->p_output_latency_function = cubeb_output_latency_load_method(android_version); ctx->p_output_latency_function =
cubeb_output_latency_load_method(android_version);
if (!cubeb_output_latency_method_is_loaded(ctx->p_output_latency_function)) { if (!cubeb_output_latency_method_is_loaded(ctx->p_output_latency_function)) {
LOG("Warning: output latency is not available, cubeb_stream_get_position() is not supported"); LOG("Warning: output latency is not available, cubeb_stream_get_position() "
"is not supported");
} }
*context = ctx; *context = ctx;
@ -774,7 +786,8 @@ opensl_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;
@ -793,11 +806,13 @@ opensl_destroy(cubeb * ctx)
free(ctx); free(ctx);
} }
static void opensl_stream_destroy(cubeb_stream * stm); static void
opensl_stream_destroy(cubeb_stream * stm);
#if defined(__ANDROID__) && (__ANDROID_API__ >= ANDROID_VERSION_LOLLIPOP) #if defined(__ANDROID__) && (__ANDROID_API__ >= ANDROID_VERSION_LOLLIPOP)
static int static int
opensl_set_format_ext(SLAndroidDataFormat_PCM_EX * format, cubeb_stream_params * params) opensl_set_format_ext(SLAndroidDataFormat_PCM_EX * format,
cubeb_stream_params * params)
{ {
assert(format); assert(format);
assert(params); assert(params);
@ -806,9 +821,9 @@ opensl_set_format_ext(SLAndroidDataFormat_PCM_EX * format, cubeb_stream_params *
format->numChannels = params->channels; format->numChannels = params->channels;
// sampleRate is in milliHertz // sampleRate is in milliHertz
format->sampleRate = params->rate * 1000; format->sampleRate = params->rate * 1000;
format->channelMask = params->channels == 1 ? format->channelMask = params->channels == 1
SL_SPEAKER_FRONT_CENTER : ? SL_SPEAKER_FRONT_CENTER
SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT; : SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT;
switch (params->format) { switch (params->format) {
case CUBEB_SAMPLE_S16LE: case CUBEB_SAMPLE_S16LE:
@ -854,9 +869,9 @@ opensl_set_format(SLDataFormat_PCM * format, cubeb_stream_params * params)
format->samplesPerSec = params->rate * 1000; format->samplesPerSec = params->rate * 1000;
format->bitsPerSample = SL_PCMSAMPLEFORMAT_FIXED_16; format->bitsPerSample = SL_PCMSAMPLEFORMAT_FIXED_16;
format->containerSize = SL_PCMSAMPLEFORMAT_FIXED_16; format->containerSize = SL_PCMSAMPLEFORMAT_FIXED_16;
format->channelMask = params->channels == 1 ? format->channelMask = params->channels == 1
SL_SPEAKER_FRONT_CENTER : ? SL_SPEAKER_FRONT_CENTER
SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT; : SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT;
switch (params->format) { switch (params->format) {
case CUBEB_SAMPLE_S16LE: case CUBEB_SAMPLE_S16LE:
@ -904,19 +919,19 @@ opensl_configure_capture(cubeb_stream * stm, cubeb_stream_params * params)
lDataSource.pLocator = &lDataLocatorIn; lDataSource.pLocator = &lDataLocatorIn;
lDataSource.pFormat = NULL; lDataSource.pFormat = NULL;
const SLInterfaceID lSoundRecorderIIDs[] = { stm->context->SL_IID_RECORD, const SLInterfaceID lSoundRecorderIIDs[] = {
stm->context->SL_IID_RECORD,
stm->context->SL_IID_ANDROIDSIMPLEBUFFERQUEUE, stm->context->SL_IID_ANDROIDSIMPLEBUFFERQUEUE,
stm->context->SL_IID_ANDROIDCONFIGURATION}; stm->context->SL_IID_ANDROIDCONFIGURATION};
const SLboolean lSoundRecorderReqs[] = { SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE }; const SLboolean lSoundRecorderReqs[] = {SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE,
SL_BOOLEAN_TRUE};
// create the audio recorder abstract object // create the audio recorder abstract object
SLresult res = (*stm->context->eng)->CreateAudioRecorder(stm->context->eng, SLresult res = (*stm->context->eng)
&stm->recorderObj, ->CreateAudioRecorder(
&lDataSource, stm->context->eng, &stm->recorderObj, &lDataSource,
&lDataSink, &lDataSink, NELEMS(lSoundRecorderIIDs),
NELEMS(lSoundRecorderIIDs), lSoundRecorderIIDs, lSoundRecorderReqs);
lSoundRecorderIIDs,
lSoundRecorderReqs);
// Sample rate not supported. Try again with default sample rate! // Sample rate not supported. Try again with default sample rate!
if (res == SL_RESULT_CONTENT_UNSUPPORTED) { if (res == SL_RESULT_CONTENT_UNSUPPORTED) {
if (stm->output_enabled && stm->output_configured_rate != 0) { if (stm->output_enabled && stm->output_configured_rate != 0) {
@ -929,13 +944,11 @@ opensl_configure_capture(cubeb_stream * stm, cubeb_stream_params * params)
stm->input_device_rate = DEFAULT_SAMPLE_RATE; stm->input_device_rate = DEFAULT_SAMPLE_RATE;
} }
lDataFormat.samplesPerSec = stm->input_device_rate * 1000; lDataFormat.samplesPerSec = stm->input_device_rate * 1000;
res = (*stm->context->eng)->CreateAudioRecorder(stm->context->eng, res = (*stm->context->eng)
&stm->recorderObj, ->CreateAudioRecorder(stm->context->eng, &stm->recorderObj,
&lDataSource, &lDataSource, &lDataSink,
&lDataSink,
NELEMS(lSoundRecorderIIDs), NELEMS(lSoundRecorderIIDs),
lSoundRecorderIIDs, lSoundRecorderIIDs, lSoundRecorderReqs);
lSoundRecorderReqs);
if (res != SL_RESULT_SUCCESS) { if (res != SL_RESULT_SUCCESS) {
LOG("Failed to create recorder. Error code: %lu", res); LOG("Failed to create recorder. Error code: %lu", res);
@ -943,7 +956,6 @@ opensl_configure_capture(cubeb_stream * stm, cubeb_stream_params * params)
} }
} }
if (get_android_version() > ANDROID_VERSION_JELLY_BEAN) { if (get_android_version() > ANDROID_VERSION_JELLY_BEAN) {
SLAndroidConfigurationItf recorderConfig; SLAndroidConfigurationItf recorderConfig;
res = (*stm->recorderObj) res = (*stm->recorderObj)
@ -952,7 +964,8 @@ opensl_configure_capture(cubeb_stream * stm, cubeb_stream_params * params)
&recorderConfig); &recorderConfig);
if (res != SL_RESULT_SUCCESS) { if (res != SL_RESULT_SUCCESS) {
LOG("Failed to get the android configuration interface for recorder. Error " LOG("Failed to get the android configuration interface for recorder. "
"Error "
"code: %lu", "code: %lu",
res); res);
return CUBEB_ERROR; return CUBEB_ERROR;
@ -960,16 +973,19 @@ opensl_configure_capture(cubeb_stream * stm, cubeb_stream_params * params)
// Voice recognition is the lowest latency, according to the docs. Camcorder // Voice recognition is the lowest latency, according to the docs. Camcorder
// uses a microphone that is in the same direction as the camera. // uses a microphone that is in the same direction as the camera.
SLint32 streamType = stm->voice_input ? SL_ANDROID_RECORDING_PRESET_VOICE_RECOGNITION SLint32 streamType = stm->voice_input
? SL_ANDROID_RECORDING_PRESET_VOICE_RECOGNITION
: SL_ANDROID_RECORDING_PRESET_CAMCORDER; : SL_ANDROID_RECORDING_PRESET_CAMCORDER;
res = (*recorderConfig) res =
(*recorderConfig)
->SetConfiguration(recorderConfig, SL_ANDROID_KEY_RECORDING_PRESET, ->SetConfiguration(recorderConfig, SL_ANDROID_KEY_RECORDING_PRESET,
&streamType, sizeof(SLint32)); &streamType, sizeof(SLint32));
if (res != SL_RESULT_SUCCESS) { if (res != SL_RESULT_SUCCESS) {
LOG("Failed to set the android configuration to VOICE for the recorder. " LOG("Failed to set the android configuration to VOICE for the recorder. "
"Error code: %lu", res); "Error code: %lu",
res);
return CUBEB_ERROR; return CUBEB_ERROR;
} }
} }
@ -980,15 +996,16 @@ opensl_configure_capture(cubeb_stream * stm, cubeb_stream_params * params)
return CUBEB_ERROR; return CUBEB_ERROR;
} }
// get the record interface // get the record interface
res = (*stm->recorderObj)->GetInterface(stm->recorderObj, res = (*stm->recorderObj)
stm->context->SL_IID_RECORD, ->GetInterface(stm->recorderObj, stm->context->SL_IID_RECORD,
&stm->recorderItf); &stm->recorderItf);
if (res != SL_RESULT_SUCCESS) { if (res != SL_RESULT_SUCCESS) {
LOG("Failed to get recorder interface. Error code: %lu", res); LOG("Failed to get recorder interface. Error code: %lu", res);
return CUBEB_ERROR; return CUBEB_ERROR;
} }
res = (*stm->recorderItf)->RegisterCallback(stm->recorderItf, recorder_marker_callback, stm); res = (*stm->recorderItf)
->RegisterCallback(stm->recorderItf, recorder_marker_callback, stm);
if (res != SL_RESULT_SUCCESS) { if (res != SL_RESULT_SUCCESS) {
LOG("Failed to register recorder marker callback. Error code: %lu", res); LOG("Failed to register recorder marker callback. Error code: %lu", res);
return CUBEB_ERROR; return CUBEB_ERROR;
@ -996,17 +1013,22 @@ opensl_configure_capture(cubeb_stream * stm, cubeb_stream_params * params)
(*stm->recorderItf)->SetMarkerPosition(stm->recorderItf, (SLmillisecond)0); (*stm->recorderItf)->SetMarkerPosition(stm->recorderItf, (SLmillisecond)0);
res = (*stm->recorderItf)->SetCallbackEventsMask(stm->recorderItf, (SLuint32)SL_RECORDEVENT_HEADATMARKER); res = (*stm->recorderItf)
->SetCallbackEventsMask(stm->recorderItf,
(SLuint32)SL_RECORDEVENT_HEADATMARKER);
if (res != SL_RESULT_SUCCESS) { if (res != SL_RESULT_SUCCESS) {
LOG("Failed to set headatmarker event mask. Error code: %lu", res); LOG("Failed to set headatmarker event mask. Error code: %lu", res);
return CUBEB_ERROR; return CUBEB_ERROR;
} }
// get the simple android buffer queue interface // get the simple android buffer queue interface
res = (*stm->recorderObj)->GetInterface(stm->recorderObj, res = (*stm->recorderObj)
->GetInterface(stm->recorderObj,
stm->context->SL_IID_ANDROIDSIMPLEBUFFERQUEUE, stm->context->SL_IID_ANDROIDSIMPLEBUFFERQUEUE,
&stm->recorderBufferQueueItf); &stm->recorderBufferQueueItf);
if (res != SL_RESULT_SUCCESS) { if (res != SL_RESULT_SUCCESS) {
LOG("Failed to get recorder (android) buffer queue interface. Error code: %lu", res); LOG("Failed to get recorder (android) buffer queue interface. Error code: "
"%lu",
res);
return CUBEB_ERROR; return CUBEB_ERROR;
} }
@ -1016,11 +1038,11 @@ opensl_configure_capture(cubeb_stream * stm, cubeb_stream_params * params)
// Register full duplex callback instead. // Register full duplex callback instead.
rec_callback = recorder_fullduplex_callback; rec_callback = recorder_fullduplex_callback;
} }
res = (*stm->recorderBufferQueueItf)->RegisterCallback(stm->recorderBufferQueueItf, res = (*stm->recorderBufferQueueItf)
rec_callback, ->RegisterCallback(stm->recorderBufferQueueItf, rec_callback, stm);
stm);
if (res != SL_RESULT_SUCCESS) { if (res != SL_RESULT_SUCCESS) {
LOG("Failed to register recorder buffer queue callback. Error code: %lu", res); LOG("Failed to register recorder buffer queue callback. Error code: %lu",
res);
return CUBEB_ERROR; return CUBEB_ERROR;
} }
@ -1032,10 +1054,12 @@ opensl_configure_capture(cubeb_stream * stm, cubeb_stream_params * params)
stm->input_array_capacity = NBUFS; stm->input_array_capacity = NBUFS;
if (stm->output_enabled) { if (stm->output_enabled) {
// Full duplex, update capacity to hold 1 sec of data // Full duplex, update capacity to hold 1 sec of data
stm->input_array_capacity = 1 * stm->input_device_rate / stm->input_buffer_length; stm->input_array_capacity =
1 * stm->input_device_rate / stm->input_buffer_length;
} }
// Allocate input array // Allocate input array
stm->input_buffer_array = (void**)calloc(1, sizeof(void*)*stm->input_array_capacity); stm->input_buffer_array =
(void **)calloc(1, sizeof(void *) * stm->input_array_capacity);
// Buffering has not started yet. // Buffering has not started yet.
stm->input_buffer_index = -1; stm->input_buffer_index = -1;
// Prepare input buffers // Prepare input buffers
@ -1063,14 +1087,17 @@ opensl_configure_capture(cubeb_stream * stm, cubeb_stream_params * params)
} }
static int static int
opensl_configure_playback(cubeb_stream * stm, cubeb_stream_params * params) { opensl_configure_playback(cubeb_stream * stm, cubeb_stream_params * params)
{
assert(stm); assert(stm);
assert(params); assert(params);
stm->user_output_rate = params->rate; stm->user_output_rate = params->rate;
if(params->format == CUBEB_SAMPLE_S16NE || params->format == CUBEB_SAMPLE_S16BE) { if (params->format == CUBEB_SAMPLE_S16NE ||
params->format == CUBEB_SAMPLE_S16BE) {
stm->framesize = params->channels * sizeof(int16_t); stm->framesize = params->channels * sizeof(int16_t);
} else if(params->format == CUBEB_SAMPLE_FLOAT32NE || params->format == CUBEB_SAMPLE_FLOAT32BE) { } else if (params->format == CUBEB_SAMPLE_FLOAT32NE ||
params->format == CUBEB_SAMPLE_FLOAT32BE) {
stm->framesize = params->channels * sizeof(float); stm->framesize = params->channels * sizeof(float);
} }
stm->lastPosition = -1; stm->lastPosition = -1;
@ -1128,13 +1155,9 @@ opensl_configure_playback(cubeb_stream * stm, cubeb_stream_params * params) {
uint32_t preferred_sampling_rate = stm->user_output_rate; uint32_t preferred_sampling_rate = stm->user_output_rate;
SLresult res = SL_RESULT_CONTENT_UNSUPPORTED; SLresult res = SL_RESULT_CONTENT_UNSUPPORTED;
if (preferred_sampling_rate) { if (preferred_sampling_rate) {
res = (*stm->context->eng)->CreateAudioPlayer(stm->context->eng, res = (*stm->context->eng)
&stm->playerObj, ->CreateAudioPlayer(stm->context->eng, &stm->playerObj, &source,
&source, &sink, NELEMS(ids), ids, req);
&sink,
NELEMS(ids),
ids,
req);
} }
// Sample rate not supported? Try again with primary sample rate! // Sample rate not supported? Try again with primary sample rate!
@ -1142,13 +1165,9 @@ opensl_configure_playback(cubeb_stream * stm, cubeb_stream_params * params) {
preferred_sampling_rate != DEFAULT_SAMPLE_RATE) { preferred_sampling_rate != DEFAULT_SAMPLE_RATE) {
preferred_sampling_rate = DEFAULT_SAMPLE_RATE; preferred_sampling_rate = DEFAULT_SAMPLE_RATE;
*format_sample_rate = preferred_sampling_rate * 1000; *format_sample_rate = preferred_sampling_rate * 1000;
res = (*stm->context->eng)->CreateAudioPlayer(stm->context->eng, res = (*stm->context->eng)
&stm->playerObj, ->CreateAudioPlayer(stm->context->eng, &stm->playerObj, &source,
&source, &sink, NELEMS(ids), ids, req);
&sink,
NELEMS(ids),
ids,
req);
} }
if (res != SL_RESULT_SUCCESS) { if (res != SL_RESULT_SUCCESS) {
@ -1164,7 +1183,8 @@ opensl_configure_playback(cubeb_stream * stm, cubeb_stream_params * params) {
stm->queuebuf_capacity = NBUFS; stm->queuebuf_capacity = NBUFS;
if (stm->output_enabled) { if (stm->output_enabled) {
// Full duplex, update capacity to hold 1 sec of data // Full duplex, update capacity to hold 1 sec of data
stm->queuebuf_capacity = 1 * stm->output_configured_rate / stm->queuebuf_len; stm->queuebuf_capacity =
1 * stm->output_configured_rate / stm->queuebuf_len;
} }
// Allocate input array // Allocate input array
stm->queuebuf = (void **)calloc(1, sizeof(void *) * stm->queuebuf_capacity); stm->queuebuf = (void **)calloc(1, sizeof(void *) * stm->queuebuf_capacity);
@ -1181,7 +1201,8 @@ opensl_configure_playback(cubeb_stream * stm, cubeb_stream_params * params) {
stm->context->SL_IID_ANDROIDCONFIGURATION, stm->context->SL_IID_ANDROIDCONFIGURATION,
&playerConfig); &playerConfig);
if (res != SL_RESULT_SUCCESS) { if (res != SL_RESULT_SUCCESS) {
LOG("Failed to get Android configuration interface. Error code: %lu", res); LOG("Failed to get Android configuration interface. Error code: %lu",
res);
return CUBEB_ERROR; return CUBEB_ERROR;
} }
@ -1189,10 +1210,9 @@ opensl_configure_playback(cubeb_stream * stm, cubeb_stream_params * params) {
if (stm->voice_output) { if (stm->voice_output) {
streamType = SL_ANDROID_STREAM_VOICE; streamType = SL_ANDROID_STREAM_VOICE;
} }
res = (*playerConfig)->SetConfiguration(playerConfig, res = (*playerConfig)
SL_ANDROID_KEY_STREAM_TYPE, ->SetConfiguration(playerConfig, SL_ANDROID_KEY_STREAM_TYPE,
&streamType, &streamType, sizeof(streamType));
sizeof(streamType));
if (res != SL_RESULT_SUCCESS) { if (res != SL_RESULT_SUCCESS) {
LOG("Failed to set Android configuration to %d Error code: %lu", LOG("Failed to set Android configuration to %d Error code: %lu",
streamType, res); streamType, res);
@ -1203,13 +1223,14 @@ opensl_configure_playback(cubeb_stream * stm, cubeb_stream_params * params) {
performanceMode = SL_ANDROID_PERFORMANCE_POWER_SAVING; performanceMode = SL_ANDROID_PERFORMANCE_POWER_SAVING;
} }
res = (*playerConfig)->SetConfiguration(playerConfig, res = (*playerConfig)
SL_ANDROID_KEY_PERFORMANCE_MODE, ->SetConfiguration(playerConfig, SL_ANDROID_KEY_PERFORMANCE_MODE,
&performanceMode, &performanceMode, sizeof(performanceMode));
sizeof(performanceMode));
if (res != SL_RESULT_SUCCESS) { if (res != SL_RESULT_SUCCESS) {
LOG("Failed to set Android performance mode to %d Error code: %lu. This is" LOG("Failed to set Android performance mode to %d Error code: %lu. This "
" not fatal", performanceMode, res); "is"
" not fatal",
performanceMode, res);
} }
} }
@ -1233,10 +1254,10 @@ opensl_configure_playback(cubeb_stream * stm, cubeb_stream_params * params) {
SLuint32 paramSize = sizeof(SLuint32); SLuint32 paramSize = sizeof(SLuint32);
// The reported latency is in milliseconds. // The reported latency is in milliseconds.
if (playerConfig) { if (playerConfig) {
res = (*playerConfig)->GetConfiguration(playerConfig, res = (*playerConfig)
->GetConfiguration(playerConfig,
(const SLchar *)"androidGetAudioLatency", (const SLchar *)"androidGetAudioLatency",
&paramSize, &paramSize, &audioLatency);
&audioLatency);
if (res == SL_RESULT_SUCCESS) { if (res == SL_RESULT_SUCCESS) {
LOG("Got playback latency using android configuration extension"); LOG("Got playback latency using android configuration extension");
stm->output_latency_ms = audioLatency; stm->output_latency_ms = audioLatency;
@ -1245,11 +1266,12 @@ opensl_configure_playback(cubeb_stream * stm, cubeb_stream_params * params) {
// `playerConfig` is available, but the above failed, or `playerConfig` is not // `playerConfig` is available, but the above failed, or `playerConfig` is not
// available. In both cases, we need to acquire the output latency by an other // available. In both cases, we need to acquire the output latency by an other
// mean. // mean.
if ((playerConfig && res != SL_RESULT_SUCCESS) || if ((playerConfig && res != SL_RESULT_SUCCESS) || !playerConfig) {
!playerConfig) { if (cubeb_output_latency_method_is_loaded(
if (cubeb_output_latency_method_is_loaded(stm->context->p_output_latency_function)) { stm->context->p_output_latency_function)) {
LOG("Got playback latency using JNI"); LOG("Got playback latency using JNI");
stm->output_latency_ms = cubeb_get_output_latency(stm->context->p_output_latency_function); stm->output_latency_ms =
cubeb_get_output_latency(stm->context->p_output_latency_function);
} else { } else {
LOG("No alternate latency querying method loaded, A/V sync will be off."); LOG("No alternate latency querying method loaded, A/V sync will be off.");
stm->output_latency_ms = 0; stm->output_latency_ms = 0;
@ -1258,24 +1280,24 @@ opensl_configure_playback(cubeb_stream * stm, cubeb_stream_params * params) {
LOG("Audio output latency: %dms", stm->output_latency_ms); LOG("Audio output latency: %dms", stm->output_latency_ms);
res = (*stm->playerObj)->GetInterface(stm->playerObj, res =
stm->context->SL_IID_PLAY, (*stm->playerObj)
&stm->play); ->GetInterface(stm->playerObj, stm->context->SL_IID_PLAY, &stm->play);
if (res != SL_RESULT_SUCCESS) { if (res != SL_RESULT_SUCCESS) {
LOG("Failed to get play interface. Error code: %lu", res); LOG("Failed to get play interface. Error code: %lu", res);
return CUBEB_ERROR; return CUBEB_ERROR;
} }
res = (*stm->playerObj)->GetInterface(stm->playerObj, res = (*stm->playerObj)
stm->context->SL_IID_BUFFERQUEUE, ->GetInterface(stm->playerObj, stm->context->SL_IID_BUFFERQUEUE,
&stm->bufq); &stm->bufq);
if (res != SL_RESULT_SUCCESS) { if (res != SL_RESULT_SUCCESS) {
LOG("Failed to get bufferqueue interface. Error code: %lu", res); LOG("Failed to get bufferqueue interface. Error code: %lu", res);
return CUBEB_ERROR; return CUBEB_ERROR;
} }
res = (*stm->playerObj)->GetInterface(stm->playerObj, res = (*stm->playerObj)
stm->context->SL_IID_VOLUME, ->GetInterface(stm->playerObj, stm->context->SL_IID_VOLUME,
&stm->volume); &stm->volume);
if (res != SL_RESULT_SUCCESS) { if (res != SL_RESULT_SUCCESS) {
LOG("Failed to get volume interface. Error code: %lu", res); LOG("Failed to get volume interface. Error code: %lu", res);
@ -1291,7 +1313,9 @@ opensl_configure_playback(cubeb_stream * stm, cubeb_stream_params * params) {
// Work around wilhelm/AudioTrack badness, bug 1221228 // Work around wilhelm/AudioTrack badness, bug 1221228
(*stm->play)->SetMarkerPosition(stm->play, (SLmillisecond)0); (*stm->play)->SetMarkerPosition(stm->play, (SLmillisecond)0);
res = (*stm->play)->SetCallbackEventsMask(stm->play, (SLuint32)SL_PLAYEVENT_HEADATMARKER); res = (*stm->play)
->SetCallbackEventsMask(stm->play,
(SLuint32)SL_PLAYEVENT_HEADATMARKER);
if (res != SL_RESULT_SUCCESS) { if (res != SL_RESULT_SUCCESS) {
LOG("Failed to set headatmarker event mask. Error code: %lu", res); LOG("Failed to set headatmarker event mask. Error code: %lu", res);
return CUBEB_ERROR; return CUBEB_ERROR;
@ -1329,37 +1353,37 @@ opensl_validate_stream_param(cubeb_stream_params * stream_params)
(stream_params->channels < 1 || stream_params->channels > 32))) { (stream_params->channels < 1 || stream_params->channels > 32))) {
return CUBEB_ERROR_INVALID_FORMAT; return CUBEB_ERROR_INVALID_FORMAT;
} }
if ((stream_params && if ((stream_params && (stream_params->prefs & CUBEB_STREAM_PREF_LOOPBACK))) {
(stream_params->prefs & CUBEB_STREAM_PREF_LOOPBACK))) {
LOG("Loopback is not supported"); LOG("Loopback is not supported");
return CUBEB_ERROR_NOT_SUPPORTED; return CUBEB_ERROR_NOT_SUPPORTED;
} }
return CUBEB_OK; return CUBEB_OK;
} }
int has_pref_set(cubeb_stream_params* input_params, int
cubeb_stream_params* output_params, has_pref_set(cubeb_stream_params * input_params,
cubeb_stream_prefs pref) cubeb_stream_params * output_params, cubeb_stream_prefs pref)
{ {
return (input_params && input_params->prefs & pref) || return (input_params && input_params->prefs & pref) ||
(output_params && output_params->prefs & pref); (output_params && output_params->prefs & pref);
} }
static int static int
opensl_stream_init(cubeb * ctx, cubeb_stream ** stream, char const * stream_name, opensl_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_frames, unsigned int latency_frames,
cubeb_data_callback data_callback, cubeb_state_callback state_callback, cubeb_data_callback data_callback,
void * user_ptr) cubeb_state_callback state_callback, void * user_ptr)
{ {
cubeb_stream * stm; cubeb_stream * stm;
assert(ctx); assert(ctx);
if (input_device || output_device) { if (input_device || output_device) {
LOG("Device selection is not supported in Android. The default will be used"); LOG("Device selection is not supported in Android. The default will be "
"used");
} }
*stream = NULL; *stream = NULL;
@ -1382,14 +1406,18 @@ opensl_stream_init(cubeb * ctx, cubeb_stream ** stream, char const * stream_name
stm->data_callback = data_callback; stm->data_callback = data_callback;
stm->state_callback = state_callback; stm->state_callback = state_callback;
stm->user_ptr = user_ptr; stm->user_ptr = user_ptr;
stm->buffer_size_frames = latency_frames ? latency_frames : DEFAULT_NUM_OF_FRAMES; stm->buffer_size_frames =
latency_frames ? latency_frames : DEFAULT_NUM_OF_FRAMES;
stm->input_enabled = (input_stream_params) ? 1 : 0; stm->input_enabled = (input_stream_params) ? 1 : 0;
stm->output_enabled = (output_stream_params) ? 1 : 0; stm->output_enabled = (output_stream_params) ? 1 : 0;
stm->shutdown = 1; stm->shutdown = 1;
stm->voice_input = has_pref_set(input_stream_params, NULL, CUBEB_STREAM_PREF_VOICE); stm->voice_input =
stm->voice_output = has_pref_set(NULL, output_stream_params, CUBEB_STREAM_PREF_VOICE); has_pref_set(input_stream_params, NULL, CUBEB_STREAM_PREF_VOICE);
stm->voice_output =
has_pref_set(NULL, output_stream_params, CUBEB_STREAM_PREF_VOICE);
LOG("cubeb stream prefs: voice_input: %s voice_output: %s", stm->voice_input ? "true" : "false", LOG("cubeb stream prefs: voice_input: %s voice_output: %s",
stm->voice_input ? "true" : "false",
stm->voice_output ? "true" : "false"); stm->voice_output ? "true" : "false");
#ifdef DEBUG #ifdef DEBUG
@ -1403,7 +1431,8 @@ opensl_stream_init(cubeb * ctx, cubeb_stream ** stream, char const * stream_name
assert(r == 0); assert(r == 0);
if (output_stream_params) { if (output_stream_params) {
LOG("Playback params: Rate %d, channels %d, format %d, latency in frames %d.", LOG("Playback params: Rate %d, channels %d, format %d, latency in frames "
"%d.",
output_stream_params->rate, output_stream_params->channels, output_stream_params->rate, output_stream_params->channels,
output_stream_params->format, stm->buffer_size_frames); output_stream_params->format, stm->buffer_size_frames);
r = opensl_configure_playback(stm, output_stream_params); r = opensl_configure_playback(stm, output_stream_params);
@ -1414,7 +1443,8 @@ opensl_stream_init(cubeb * ctx, cubeb_stream ** stream, char const * stream_name
} }
if (input_stream_params) { if (input_stream_params) {
LOG("Capture params: Rate %d, channels %d, format %d, latency in frames %d.", LOG("Capture params: Rate %d, channels %d, format %d, latency in frames "
"%d.",
input_stream_params->rate, input_stream_params->channels, input_stream_params->rate, input_stream_params->channels,
input_stream_params->format, stm->buffer_size_frames); input_stream_params->format, stm->buffer_size_frames);
r = opensl_configure_capture(stm, input_stream_params); r = opensl_configure_capture(stm, input_stream_params);
@ -1446,13 +1476,10 @@ opensl_stream_init(cubeb * ctx, cubeb_stream ** stream, char const * stream_name
output_params.rate = stm->output_configured_rate; output_params.rate = stm->output_configured_rate;
} }
stm->resampler = cubeb_resampler_create(stm, stm->resampler = cubeb_resampler_create(
input_stream_params ? &input_params : NULL, stm, input_stream_params ? &input_params : NULL,
output_stream_params ? &output_params : NULL, output_stream_params ? &output_params : NULL, target_sample_rate,
target_sample_rate, data_callback, user_ptr, CUBEB_RESAMPLER_QUALITY_DEFAULT);
data_callback,
user_ptr,
CUBEB_RESAMPLER_QUALITY_DEFAULT);
if (!stm->resampler) { if (!stm->resampler) {
LOG("Failed to create resampler"); LOG("Failed to create resampler");
opensl_stream_destroy(stm); opensl_stream_destroy(stm);
@ -1487,7 +1514,9 @@ opensl_start_recorder(cubeb_stream * stm)
SLuint32 recorderState; SLuint32 recorderState;
(*stm->recorderObj)->GetState(stm->recorderObj, &recorderState); (*stm->recorderObj)->GetState(stm->recorderObj, &recorderState);
if (recorderState == SL_OBJECT_STATE_REALIZED) { if (recorderState == SL_OBJECT_STATE_REALIZED) {
SLresult res = (*stm->recorderItf)->SetRecordState(stm->recorderItf, SL_RECORDSTATE_RECORDING); SLresult res =
(*stm->recorderItf)
->SetRecordState(stm->recorderItf, SL_RECORDSTATE_RECORDING);
if (res != SL_RESULT_SUCCESS) { if (res != SL_RESULT_SUCCESS) {
LOG("Failed to start recorder. Error code: %lu", res); LOG("Failed to start recorder. Error code: %lu", res);
return CUBEB_ERROR; return CUBEB_ERROR;
@ -1548,7 +1577,8 @@ opensl_stop_recorder(cubeb_stream * stm)
assert(stm->recorderObj); assert(stm->recorderObj);
assert(stm->shutdown || stm->draining); assert(stm->shutdown || stm->draining);
SLresult res = (*stm->recorderItf)->SetRecordState(stm->recorderItf, SL_RECORDSTATE_PAUSED); SLresult res = (*stm->recorderItf)
->SetRecordState(stm->recorderItf, SL_RECORDSTATE_PAUSED);
if (res != SL_RESULT_SUCCESS) { if (res != SL_RESULT_SUCCESS) {
LOG("Failed to stop recorder. Error code: %lu", res); LOG("Failed to stop recorder. Error code: %lu", res);
return CUBEB_ERROR; return CUBEB_ERROR;
@ -1594,7 +1624,8 @@ opensl_destroy_recorder(cubeb_stream * stm)
assert(stm->recorderObj); assert(stm->recorderObj);
if (stm->recorderBufferQueueItf) { if (stm->recorderBufferQueueItf) {
SLresult res = (*stm->recorderBufferQueueItf)->Clear(stm->recorderBufferQueueItf); SLresult res =
(*stm->recorderBufferQueueItf)->Clear(stm->recorderBufferQueueItf);
if (res != SL_RESULT_SUCCESS) { if (res != SL_RESULT_SUCCESS) {
LOG("Failed to clear recorder buffer queue. Error code: %lu", res); LOG("Failed to clear recorder buffer queue. Error code: %lu", res);
return CUBEB_ERROR; return CUBEB_ERROR;
@ -1662,7 +1693,8 @@ opensl_stream_get_position(cubeb_stream * stm, uint64_t * position)
clock_gettime(CLOCK_MONOTONIC, &t); clock_gettime(CLOCK_MONOTONIC, &t);
if (stm->lastPosition == msec) { if (stm->lastPosition == msec) {
compensation_msec = compensation_msec =
(t.tv_sec*1000000000LL + t.tv_nsec - stm->lastPositionTimeStamp) / 1000000; (t.tv_sec * 1000000000LL + t.tv_nsec - stm->lastPositionTimeStamp) /
1000000;
} else { } else {
stm->lastPositionTimeStamp = t.tv_sec * 1000000000LL + t.tv_nsec; stm->lastPositionTimeStamp = t.tv_sec * 1000000000LL + t.tv_nsec;
stm->lastPosition = msec; stm->lastPosition = msec;
@ -1672,7 +1704,8 @@ opensl_stream_get_position(cubeb_stream * stm, uint64_t * position)
uint32_t output_latency = stm->output_latency_ms; uint32_t output_latency = stm->output_latency_ms;
pthread_mutex_lock(&stm->mutex); pthread_mutex_lock(&stm->mutex);
int64_t maximum_position = stm->written * (int64_t)stm->user_output_rate / stm->output_configured_rate; int64_t maximum_position = stm->written * (int64_t)stm->user_output_rate /
stm->output_configured_rate;
pthread_mutex_unlock(&stm->mutex); pthread_mutex_unlock(&stm->mutex);
assert(maximum_position >= 0); assert(maximum_position >= 0);
@ -1687,8 +1720,8 @@ opensl_stream_get_position(cubeb_stream * stm, uint64_t * position)
samplerate * (msec - output_latency + compensation_msec) / 1000; samplerate * (msec - output_latency + compensation_msec) / 1000;
stm->lastCompensativePosition = msec + compensation_msec; stm->lastCompensativePosition = msec + compensation_msec;
} }
*position = unadjusted_position < maximum_position ? *position = unadjusted_position < maximum_position ? unadjusted_position
unadjusted_position : maximum_position; : maximum_position;
} else { } else {
*position = 0; *position = 0;
} }
@ -1702,7 +1735,7 @@ opensl_stream_get_latency(cubeb_stream * stm, uint32_t * latency)
assert(latency); assert(latency);
uint32_t stream_latency_frames = uint32_t stream_latency_frames =
stm->user_output_rate * (stm->output_latency_ms / 1000); stm->user_output_rate * stm->output_latency_ms / 1000;
return stream_latency_frames + cubeb_resampler_latency(stm->resampler); return stream_latency_frames + cubeb_resampler_latency(stm->resampler);
} }
@ -1751,7 +1784,6 @@ static struct cubeb_ops const opensl_ops = {
.stream_destroy = opensl_stream_destroy, .stream_destroy = opensl_stream_destroy,
.stream_start = opensl_stream_start, .stream_start = opensl_stream_start,
.stream_stop = opensl_stream_stop, .stream_stop = opensl_stream_stop,
.stream_reset_default_device = NULL,
.stream_get_position = opensl_stream_get_position, .stream_get_position = opensl_stream_get_position,
.stream_get_latency = opensl_stream_get_latency, .stream_get_latency = opensl_stream_get_latency,
.stream_get_input_latency = NULL, .stream_get_input_latency = NULL,
@ -1760,5 +1792,4 @@ static struct cubeb_ops const opensl_ops = {
.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

@ -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
@ -96,6 +96,9 @@ 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;
@ -126,9 +129,6 @@ struct cubeb_stream {
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 *
@ -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;
@ -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;
@ -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;
@ -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;
} }
@ -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,49 +745,143 @@ 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];
unsigned int ppending, rpending;
pfds[0].fd = s->play.fd;
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)) {
LOG("Error %d occured when setting trigger on record fd", errno);
state = CUBEB_STATE_ERROR;
goto breakdown;
}
trig |= PCM_ENABLE_INPUT;
if (ioctl(s->record.fd, SNDCTL_DSP_SETTRIGGER, &trig)) {
LOG("Error %d occured when setting trigger on record fd", errno);
state = CUBEB_STATE_ERROR;
goto breakdown;
}
memset(s->record.buf, 0, s->bufframes * s->record.frame_size);
}
while (1) {
long nfr = 0; long nfr = 0;
pthread_mutex_lock(&s->mtx); if (record_on) {
if (!s->running || s->destroying) { if (ioctl(s->record.fd, SNDCTL_DSP_SETTRIGGER, &trig)) {
pthread_mutex_unlock(&s->mtx); LOG("Error %d occured when setting trigger on record fd", errno);
break; state = CUBEB_STATE_ERROR;
goto breakdown;
} }
pthread_mutex_unlock(&s->mtx);
if (s->play.fd == -1 && s->record.fd == -1) { trig |= PCM_ENABLE_INPUT;
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);
state = CUBEB_STATE_ERROR;
goto breakdown;
}
}
if (!play_on && !record_on) {
/* /*
* Stop here if the stream is not play & record stream, * Stop here if the stream is not play & record stream,
* play-only stream or record-only stream * play-only stream or record-only stream
@ -792,22 +890,46 @@ oss_audio_loop(cubeb_stream * s, cubeb_state *new_state)
goto breakdown; goto breakdown;
} }
while ((s->bufframes - ppending) >= s->nfr && rpending >= s->nfr) { while (1) {
long n = ((s->bufframes - ppending) < rpending) ? s->bufframes - ppending : rpending; pthread_mutex_lock(&s->mtx);
char *rptr = NULL, *pptr = NULL; if (!s->running || s->destroying) {
if (s->record.fd != -1) pthread_mutex_unlock(&s->mtx);
rptr = (char *)s->record.buf; break;
if (s->play.fd != -1)
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); pthread_mutex_unlock(&s->mtx);
if (nfr == CUBEB_ERROR) {
long got = 0;
if (nfr > 0) {
if (record_on) {
if (oss_get_rec_frames(s, nfr) == CUBEB_ERROR) {
state = CUBEB_STATE_ERROR; state = CUBEB_STATE_ERROR;
goto breakdown; goto breakdown;
} }
if (pptr) { if (s->record.floating) {
oss_linear32_to_float(s->record.buf, s->record.info.channels * nfr);
}
}
got = s->data_cb(s, s->user_ptr, s->record.buf, s->play.buf, nfr);
if (got == CUBEB_ERROR) {
state = CUBEB_STATE_ERROR;
goto breakdown;
}
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,98 +937,14 @@ 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; state = CUBEB_STATE_ERROR;
goto breakdown; 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;
goto breakdown;
}
frames = n / s->play.frame_size;
pthread_mutex_lock(&s->mtx);
s->frames_written += frames;
pthread_mutex_unlock(&s->mtx);
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) {
size_t bytes = (s->bufframes - rpending) * s->record.frame_size;
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) { if (drain) {
@ -915,6 +953,12 @@ oss_audio_loop(cubeb_stream * s, cubeb_state *new_state)
} }
} }
if (oss_wait_fds_for_space(s, &nfr) != 0) {
state = CUBEB_STATE_ERROR;
goto breakdown;
}
}
return 1; return 1;
breakdown: breakdown:
@ -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,20 +1027,15 @@ 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;
@ -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,21 +1106,21 @@ 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) {
LOG("Setting play params failed"); LOG("Setting play params failed");
@ -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)
} }

View file

@ -5,21 +5,21 @@
* accompanying file LICENSE for details. * accompanying file LICENSE for details.
*/ */
#undef NDEBUG #undef NDEBUG
#include "cubeb-internal.h"
#include "cubeb/cubeb.h"
#include "cubeb_mixer.h"
#include "cubeb_strings.h"
#include <assert.h> #include <assert.h>
#include <dlfcn.h> #include <dlfcn.h>
#include <pulse/pulseaudio.h> #include <pulse/pulseaudio.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include "cubeb-internal.h"
#include "cubeb/cubeb.h"
#include "cubeb_mixer.h"
#include "cubeb_strings.h"
#ifdef DISABLE_LIBPULSE_DLOPEN #ifdef DISABLE_LIBPULSE_DLOPEN
#define WRAP(x) x #define WRAP(x) x
#else #else
#define WRAP(x) cubeb_##x #define WRAP(x) (*cubeb_##x)
#define LIBPULSE_API_VISIT(X) \ #define LIBPULSE_API_VISIT(X) \
X(pa_channel_map_can_balance) \ X(pa_channel_map_can_balance) \
X(pa_channel_map_init) \ X(pa_channel_map_init) \
@ -86,7 +86,7 @@
X(pa_mainloop_api_once) \ X(pa_mainloop_api_once) \
X(pa_get_library_version) \ X(pa_get_library_version) \
X(pa_channel_map_init_auto) \ X(pa_channel_map_init_auto) \
X(pa_stream_set_name) \ X(pa_stream_set_name)
#define MAKE_TYPEDEF(x) static typeof(x) * cubeb_##x; #define MAKE_TYPEDEF(x) static typeof(x) * cubeb_##x;
LIBPULSE_API_VISIT(MAKE_TYPEDEF); LIBPULSE_API_VISIT(MAKE_TYPEDEF);
@ -139,11 +139,7 @@ struct cubeb_stream {
static const float PULSE_NO_GAIN = -1.0; static const float PULSE_NO_GAIN = -1.0;
enum cork_state { enum cork_state { UNCORK = 0, CORK = 1 << 0, NOTIFY = 1 << 1 };
UNCORK = 0,
CORK = 1 << 0,
NOTIFY = 1 << 1
};
static int static int
intern_device_id(cubeb * ctx, char const ** id) intern_device_id(cubeb * ctx, char const ** id)
@ -164,14 +160,16 @@ intern_device_id(cubeb * ctx, char const ** id)
} }
static void static void
sink_info_callback(pa_context * context, const pa_sink_info * info, int eol, void * u) sink_info_callback(pa_context * context, const pa_sink_info * info, int eol,
void * u)
{ {
(void)context; (void)context;
cubeb * ctx = u; cubeb * ctx = u;
if (!eol) { if (!eol) {
free(ctx->default_sink_info); free(ctx->default_sink_info);
ctx->default_sink_info = malloc(sizeof(struct cubeb_default_sink_info)); ctx->default_sink_info = malloc(sizeof(struct cubeb_default_sink_info));
memcpy(&ctx->default_sink_info->channel_map, &info->channel_map, sizeof(pa_channel_map)); memcpy(&ctx->default_sink_info->channel_map, &info->channel_map,
sizeof(pa_channel_map));
ctx->default_sink_info->sample_spec_rate = info->sample_spec.rate; ctx->default_sink_info->sample_spec_rate = info->sample_spec.rate;
ctx->default_sink_info->flags = info->flags; ctx->default_sink_info->flags = info->flags;
} }
@ -179,10 +177,12 @@ sink_info_callback(pa_context * context, const pa_sink_info * info, int eol, voi
} }
static void static void
server_info_callback(pa_context * context, const pa_server_info * info, void * u) server_info_callback(pa_context * context, const pa_server_info * info,
void * u)
{ {
pa_operation * o; pa_operation * o;
o = WRAP(pa_context_get_sink_info_by_name)(context, info->default_sink_name, sink_info_callback, u); o = WRAP(pa_context_get_sink_info_by_name)(context, info->default_sink_name,
sink_info_callback, u);
if (o) { if (o) {
WRAP(pa_operation_unref)(o); WRAP(pa_operation_unref)(o);
} }
@ -223,7 +223,8 @@ stream_state_change_callback(cubeb_stream * stm, cubeb_state s)
} }
static void static void
stream_drain_callback(pa_mainloop_api * a, pa_time_event * e, struct timeval const * tv, void * u) stream_drain_callback(pa_mainloop_api * a, pa_time_event * e,
struct timeval const * tv, void * u)
{ {
(void)a; (void)a;
(void)tv; (void)tv;
@ -247,7 +248,8 @@ stream_state_callback(pa_stream * s, void * u)
} }
static void static void
trigger_user_callback(pa_stream * s, void const * input_data, size_t nbytes, cubeb_stream * stm) trigger_user_callback(pa_stream * s, void const * input_data, size_t nbytes,
cubeb_stream * stm)
{ {
void * buffer; void * buffer;
size_t size; size_t size;
@ -264,13 +266,17 @@ trigger_user_callback(pa_stream * s, void const * input_data, size_t nbytes, cub
while (towrite) { while (towrite) {
size = towrite; size = towrite;
r = WRAP(pa_stream_begin_write)(s, &buffer, &size); r = WRAP(pa_stream_begin_write)(s, &buffer, &size);
// Note: this has failed running under rr on occassion - needs investigation. // Note: this has failed running under rr on occassion - needs
// investigation.
assert(r == 0); assert(r == 0);
assert(size > 0); assert(size > 0);
assert(size % frame_size == 0); assert(size % frame_size == 0);
LOGV("Trigger user callback with output buffer size=%zd, read_offset=%zd", size, read_offset); LOGV("Trigger user callback with output buffer size=%zd, read_offset=%zd",
got = stm->data_callback(stm, stm->user_ptr, (uint8_t const *)input_data + read_offset, buffer, size / frame_size); size, read_offset);
got = stm->data_callback(stm, stm->user_ptr,
(uint8_t const *)input_data + read_offset, buffer,
size / frame_size);
if (got < 0) { if (got < 0) {
WRAP(pa_stream_cancel_write)(s); WRAP(pa_stream_cancel_write)(s);
stm->shutdown = 1; stm->shutdown = 1;
@ -299,7 +305,8 @@ trigger_user_callback(pa_stream * s, void const * input_data, size_t nbytes, cub
} }
} }
r = WRAP(pa_stream_write)(s, buffer, got * frame_size, NULL, 0, PA_SEEK_RELATIVE); r = WRAP(pa_stream_write)(s, buffer, got * frame_size, NULL, 0,
PA_SEEK_RELATIVE);
assert(r == 0); assert(r == 0);
if ((size_t)got < size / frame_size) { if ((size_t)got < size / frame_size) {
@ -313,7 +320,9 @@ trigger_user_callback(pa_stream * s, void const * input_data, size_t nbytes, cub
/* pa_stream_drain is useless, see PA bug# 866. this is a workaround. */ /* pa_stream_drain is useless, see PA bug# 866. this is a workaround. */
/* arbitrary safety margin: double the current latency. */ /* arbitrary safety margin: double the current latency. */
assert(!stm->drain_timer); assert(!stm->drain_timer);
stm->drain_timer = WRAP(pa_context_rttime_new)(stm->context->context, WRAP(pa_rtclock_now)() + 2 * latency, stream_drain_callback, stm); stm->drain_timer = WRAP(pa_context_rttime_new)(
stm->context->context, WRAP(pa_rtclock_now)() + 2 * latency,
stream_drain_callback, stm);
stm->shutdown = 1; stm->shutdown = 1;
return; return;
} }
@ -341,8 +350,7 @@ stream_write_callback(pa_stream * s, size_t nbytes, void * u)
{ {
LOGV("Output callback to be written buffer size %zd", nbytes); LOGV("Output callback to be written buffer size %zd", nbytes);
cubeb_stream * stm = u; cubeb_stream * stm = u;
if (stm->shutdown || if (stm->shutdown || stm->state != CUBEB_STATE_STARTED) {
stm->state != CUBEB_STATE_STARTED) {
return; return;
} }
@ -379,7 +387,8 @@ stream_read_callback(pa_stream * s, size_t nbytes, void * u)
trigger_user_callback(stm->output_stream, read_data, write_size, stm); trigger_user_callback(stm->output_stream, read_data, write_size, stm);
} else { } else {
// input/capture only operation. Call callback directly // input/capture only operation. Call callback directly
long got = stm->data_callback(stm, stm->user_ptr, read_data, NULL, read_frames); long got = stm->data_callback(stm, stm->user_ptr, read_data, NULL,
read_frames);
if (got < 0 || (size_t)got != read_frames) { if (got < 0 || (size_t)got != read_frames) {
WRAP(pa_stream_cancel_write)(s); WRAP(pa_stream_cancel_write)(s);
stm->shutdown = 1; stm->shutdown = 1;
@ -432,11 +441,13 @@ static int
wait_until_stream_ready(cubeb_stream * stm) wait_until_stream_ready(cubeb_stream * stm)
{ {
if (stm->output_stream && if (stm->output_stream &&
wait_until_io_stream_ready(stm->output_stream, stm->context->mainloop) == -1) { wait_until_io_stream_ready(stm->output_stream, stm->context->mainloop) ==
-1) {
return -1; return -1;
} }
if (stm->input_stream && if (stm->input_stream &&
wait_until_io_stream_ready(stm->input_stream, stm->context->mainloop) == -1) { wait_until_io_stream_ready(stm->input_stream, stm->context->mainloop) ==
-1) {
return -1; return -1;
} }
return 0; return 0;
@ -464,7 +475,8 @@ cork_io_stream(cubeb_stream * stm, pa_stream * io_stream, enum cork_state state)
if (!io_stream) { if (!io_stream) {
return; return;
} }
o = WRAP(pa_stream_cork)(io_stream, state & CORK, stream_success_callback, stm); o = WRAP(pa_stream_cork)(io_stream, state & CORK, stream_success_callback,
stm);
if (o) { if (o) {
operation_wait(stm->context, io_stream, o); operation_wait(stm->context, io_stream, o);
WRAP(pa_operation_unref)(o); WRAP(pa_operation_unref)(o);
@ -491,7 +503,8 @@ stream_update_timing_info(cubeb_stream * stm)
int r = -1; int r = -1;
pa_operation * o = NULL; pa_operation * o = NULL;
if (stm->output_stream) { if (stm->output_stream) {
o = WRAP(pa_stream_update_timing_info)(stm->output_stream, stream_success_callback, stm); o = WRAP(pa_stream_update_timing_info)(stm->output_stream,
stream_success_callback, stm);
if (o) { if (o) {
r = operation_wait(stm->context, stm->output_stream, o); r = operation_wait(stm->context, stm->output_stream, o);
WRAP(pa_operation_unref)(o); WRAP(pa_operation_unref)(o);
@ -502,7 +515,8 @@ stream_update_timing_info(cubeb_stream * stm)
} }
if (stm->input_stream) { if (stm->input_stream) {
o = WRAP(pa_stream_update_timing_info)(stm->input_stream, stream_success_callback, stm); o = WRAP(pa_stream_update_timing_info)(stm->input_stream,
stream_success_callback, stm);
if (o) { if (o) {
r = operation_wait(stm->context, stm->input_stream, o); r = operation_wait(stm->context, stm->input_stream, o);
WRAP(pa_operation_unref)(o); WRAP(pa_operation_unref)(o);
@ -584,8 +598,10 @@ layout_to_channel_map(cubeb_channel_layout layout, pa_channel_map * cm)
} }
} }
static void pulse_context_destroy(cubeb * ctx); static void
static void pulse_destroy(cubeb * ctx); pulse_context_destroy(cubeb * ctx);
static void
pulse_destroy(cubeb * ctx);
static int static int
pulse_context_init(cubeb * ctx) pulse_context_init(cubeb * ctx)
@ -597,12 +613,13 @@ pulse_context_init(cubeb * ctx)
pulse_context_destroy(ctx); pulse_context_destroy(ctx);
} }
ctx->context = WRAP(pa_context_new)(WRAP(pa_threaded_mainloop_get_api)(ctx->mainloop), ctx->context = WRAP(pa_context_new)(
ctx->context_name); WRAP(pa_threaded_mainloop_get_api)(ctx->mainloop), ctx->context_name);
if (!ctx->context) { if (!ctx->context) {
return -1; return -1;
} }
WRAP(pa_context_set_state_callback)(ctx->context, context_state_callback, ctx); WRAP(pa_context_set_state_callback)
(ctx->context, context_state_callback, ctx);
WRAP(pa_threaded_mainloop_lock)(ctx->mainloop); WRAP(pa_threaded_mainloop_lock)(ctx->mainloop);
r = WRAP(pa_context_connect)(ctx->context, NULL, 0, NULL); r = WRAP(pa_context_connect)(ctx->context, NULL, 0, NULL);
@ -621,8 +638,8 @@ pulse_context_init(cubeb * ctx)
return 0; return 0;
} }
static int pulse_subscribe_notifications(cubeb * context, static int
pa_subscription_mask_t mask); pulse_subscribe_notifications(cubeb * context, pa_subscription_mask_t mask);
/*static*/ int /*static*/ int
pulse_init(cubeb ** context, char const * context_name) pulse_init(cubeb ** context, char const * context_name)
@ -642,7 +659,8 @@ pulse_init(cubeb ** context, char const * context_name)
} }
} }
#define LOAD(x) { \ #define LOAD(x) \
{ \
cubeb_##x = dlsym(libpulse, #x); \ cubeb_##x = dlsym(libpulse, #x); \
if (!cubeb_##x) { \ if (!cubeb_##x) { \
dlclose(libpulse); \ dlclose(libpulse); \
@ -735,7 +753,8 @@ pulse_get_preferred_sample_rate(cubeb * ctx, uint32_t * rate)
} }
static int static int
pulse_get_min_latency(cubeb * ctx, cubeb_stream_params params, uint32_t * latency_frames) pulse_get_min_latency(cubeb * ctx, cubeb_stream_params params,
uint32_t * latency_frames)
{ {
(void)ctx; (void)ctx;
// According to PulseAudio developers, this is a safe minimum. // According to PulseAudio developers, this is a safe minimum.
@ -785,7 +804,8 @@ pulse_destroy(cubeb * ctx)
free(ctx); free(ctx);
} }
static void pulse_stream_destroy(cubeb_stream * stm); static void
pulse_stream_destroy(cubeb_stream * stm);
static pa_sample_format_t static pa_sample_format_t
to_pulse_format(cubeb_sample_format format) to_pulse_format(cubeb_sample_format format)
@ -809,31 +829,38 @@ pulse_default_layout_for_channels(uint32_t ch)
{ {
assert(ch > 0 && ch <= 8); assert(ch > 0 && ch <= 8);
switch (ch) { switch (ch) {
case 1: return CUBEB_LAYOUT_MONO; case 1:
case 2: return CUBEB_LAYOUT_STEREO; return CUBEB_LAYOUT_MONO;
case 3: return CUBEB_LAYOUT_3F; case 2:
case 4: return CUBEB_LAYOUT_QUAD; return CUBEB_LAYOUT_STEREO;
case 5: return CUBEB_LAYOUT_3F2; case 3:
case 6: return CUBEB_LAYOUT_3F_LFE | return CUBEB_LAYOUT_3F;
CHANNEL_SIDE_LEFT | CHANNEL_SIDE_RIGHT; case 4:
case 7: return CUBEB_LAYOUT_3F3R_LFE; return CUBEB_LAYOUT_QUAD;
case 8: return CUBEB_LAYOUT_3F4_LFE; case 5:
return CUBEB_LAYOUT_3F2;
case 6:
return CUBEB_LAYOUT_3F_LFE | CHANNEL_SIDE_LEFT | CHANNEL_SIDE_RIGHT;
case 7:
return CUBEB_LAYOUT_3F3R_LFE;
case 8:
return CUBEB_LAYOUT_3F4_LFE;
} }
// Never get here! // Never get here!
return CUBEB_LAYOUT_UNDEFINED; return CUBEB_LAYOUT_UNDEFINED;
} }
static int static int
create_pa_stream(cubeb_stream * stm, create_pa_stream(cubeb_stream * stm, pa_stream ** pa_stm,
pa_stream ** pa_stm, cubeb_stream_params * stream_params, char const * stream_name)
cubeb_stream_params * stream_params,
char const * stream_name)
{ {
assert(stm && stream_params); assert(stm && stream_params);
assert(&stm->input_stream == pa_stm || (&stm->output_stream == pa_stm && assert(&stm->input_stream == pa_stm ||
(&stm->output_stream == pa_stm &&
(stream_params->layout == CUBEB_LAYOUT_UNDEFINED || (stream_params->layout == CUBEB_LAYOUT_UNDEFINED ||
(stream_params->layout != CUBEB_LAYOUT_UNDEFINED && (stream_params->layout != CUBEB_LAYOUT_UNDEFINED &&
cubeb_channel_layout_nb_channels(stream_params->layout) == stream_params->channels)))); cubeb_channel_layout_nb_channels(stream_params->layout) ==
stream_params->channels))));
if (stream_params->prefs & CUBEB_STREAM_PREF_LOOPBACK) { if (stream_params->prefs & CUBEB_STREAM_PREF_LOOPBACK) {
return CUBEB_ERROR_NOT_SUPPORTED; return CUBEB_ERROR_NOT_SUPPORTED;
} }
@ -850,13 +877,18 @@ create_pa_stream(cubeb_stream * stm,
if (stream_params->layout == CUBEB_LAYOUT_UNDEFINED) { if (stream_params->layout == CUBEB_LAYOUT_UNDEFINED) {
pa_channel_map cm; pa_channel_map cm;
if (stream_params->channels <= 8 && if (stream_params->channels <= 8 &&
!WRAP(pa_channel_map_init_auto)(&cm, stream_params->channels, PA_CHANNEL_MAP_DEFAULT)) { !WRAP(pa_channel_map_init_auto)(&cm, stream_params->channels,
LOG("Layout undefined and PulseAudio's default layout has not been configured, guess one."); PA_CHANNEL_MAP_DEFAULT)) {
layout_to_channel_map(pulse_default_layout_for_channels(stream_params->channels), &cm); LOG("Layout undefined and PulseAudio's default layout has not been "
*pa_stm = WRAP(pa_stream_new)(stm->context->context, stream_name, &ss, &cm); "configured, guess one.");
layout_to_channel_map(
pulse_default_layout_for_channels(stream_params->channels), &cm);
*pa_stm =
WRAP(pa_stream_new)(stm->context->context, stream_name, &ss, &cm);
} else { } else {
LOG("Layout undefined, PulseAudio will use its default."); LOG("Layout undefined, PulseAudio will use its default.");
*pa_stm = WRAP(pa_stream_new)(stm->context->context, stream_name, &ss, NULL); *pa_stm =
WRAP(pa_stream_new)(stm->context->context, stream_name, &ss, NULL);
} }
} else { } else {
pa_channel_map cm; pa_channel_map cm;
@ -867,7 +899,8 @@ create_pa_stream(cubeb_stream * stm,
} }
static pa_buffer_attr static pa_buffer_attr
set_buffering_attribute(unsigned int latency_frames, pa_sample_spec * sample_spec) set_buffering_attribute(unsigned int latency_frames,
pa_sample_spec * sample_spec)
{ {
pa_buffer_attr battr; pa_buffer_attr battr;
battr.maxlength = -1; battr.maxlength = -1;
@ -876,24 +909,23 @@ set_buffering_attribute(unsigned int latency_frames, pa_sample_spec * sample_spe
battr.minreq = battr.tlength / 4; battr.minreq = battr.tlength / 4;
battr.fragsize = battr.minreq; battr.fragsize = battr.minreq;
LOG("Requested buffer attributes maxlength %u, tlength %u, prebuf %u, minreq %u, fragsize %u", LOG("Requested buffer attributes maxlength %u, tlength %u, prebuf %u, minreq "
battr.maxlength, battr.tlength, battr.prebuf, battr.minreq, battr.fragsize); "%u, fragsize %u",
battr.maxlength, battr.tlength, battr.prebuf, battr.minreq,
battr.fragsize);
return battr; return battr;
} }
static int static int
pulse_stream_init(cubeb * context, pulse_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 * stm; cubeb_stream * stm;
pa_buffer_attr battr; pa_buffer_attr battr;
@ -921,22 +953,25 @@ pulse_stream_init(cubeb * context,
WRAP(pa_threaded_mainloop_lock)(stm->context->mainloop); WRAP(pa_threaded_mainloop_lock)(stm->context->mainloop);
if (output_stream_params) { if (output_stream_params) {
r = create_pa_stream(stm, &stm->output_stream, output_stream_params, stream_name); r = create_pa_stream(stm, &stm->output_stream, output_stream_params,
stream_name);
if (r != CUBEB_OK) { if (r != CUBEB_OK) {
WRAP(pa_threaded_mainloop_unlock)(stm->context->mainloop); WRAP(pa_threaded_mainloop_unlock)(stm->context->mainloop);
pulse_stream_destroy(stm); pulse_stream_destroy(stm);
return r; return r;
} }
stm->output_sample_spec = *(WRAP(pa_stream_get_sample_spec)(stm->output_stream)); stm->output_sample_spec =
*(WRAP(pa_stream_get_sample_spec)(stm->output_stream));
WRAP(pa_stream_set_state_callback)(stm->output_stream, stream_state_callback, stm); WRAP(pa_stream_set_state_callback)
WRAP(pa_stream_set_write_callback)(stm->output_stream, stream_write_callback, stm); (stm->output_stream, stream_state_callback, stm);
WRAP(pa_stream_set_write_callback)
(stm->output_stream, stream_write_callback, stm);
battr = set_buffering_attribute(latency_frames, &stm->output_sample_spec); battr = set_buffering_attribute(latency_frames, &stm->output_sample_spec);
WRAP(pa_stream_connect_playback)(stm->output_stream, WRAP(pa_stream_connect_playback)
(char const *) output_device, (stm->output_stream, (char const *)output_device, &battr,
&battr,
PA_STREAM_AUTO_TIMING_UPDATE | PA_STREAM_INTERPOLATE_TIMING | PA_STREAM_AUTO_TIMING_UPDATE | PA_STREAM_INTERPOLATE_TIMING |
PA_STREAM_START_CORKED | PA_STREAM_ADJUST_LATENCY, PA_STREAM_START_CORKED | PA_STREAM_ADJUST_LATENCY,
NULL, NULL); NULL, NULL);
@ -944,22 +979,25 @@ pulse_stream_init(cubeb * context,
// Set up input stream // Set up input stream
if (input_stream_params) { if (input_stream_params) {
r = create_pa_stream(stm, &stm->input_stream, input_stream_params, stream_name); r = create_pa_stream(stm, &stm->input_stream, input_stream_params,
stream_name);
if (r != CUBEB_OK) { if (r != CUBEB_OK) {
WRAP(pa_threaded_mainloop_unlock)(stm->context->mainloop); WRAP(pa_threaded_mainloop_unlock)(stm->context->mainloop);
pulse_stream_destroy(stm); pulse_stream_destroy(stm);
return r; return r;
} }
stm->input_sample_spec = *(WRAP(pa_stream_get_sample_spec)(stm->input_stream)); stm->input_sample_spec =
*(WRAP(pa_stream_get_sample_spec)(stm->input_stream));
WRAP(pa_stream_set_state_callback)(stm->input_stream, stream_state_callback, stm); WRAP(pa_stream_set_state_callback)
WRAP(pa_stream_set_read_callback)(stm->input_stream, stream_read_callback, stm); (stm->input_stream, stream_state_callback, stm);
WRAP(pa_stream_set_read_callback)
(stm->input_stream, stream_read_callback, stm);
battr = set_buffering_attribute(latency_frames, &stm->input_sample_spec); battr = set_buffering_attribute(latency_frames, &stm->input_sample_spec);
WRAP(pa_stream_connect_record)(stm->input_stream, WRAP(pa_stream_connect_record)
(char const *) input_device, (stm->input_stream, (char const *)input_device, &battr,
&battr,
PA_STREAM_AUTO_TIMING_UPDATE | PA_STREAM_INTERPOLATE_TIMING | PA_STREAM_AUTO_TIMING_UPDATE | PA_STREAM_INTERPOLATE_TIMING |
PA_STREAM_START_CORKED | PA_STREAM_ADJUST_LATENCY); PA_STREAM_START_CORKED | PA_STREAM_ADJUST_LATENCY);
} }
@ -982,15 +1020,19 @@ pulse_stream_init(cubeb * context,
if (output_stream_params) { if (output_stream_params) {
const pa_buffer_attr * output_att; const pa_buffer_attr * output_att;
output_att = WRAP(pa_stream_get_buffer_attr)(stm->output_stream); output_att = WRAP(pa_stream_get_buffer_attr)(stm->output_stream);
LOG("Output buffer attributes maxlength %u, tlength %u, prebuf %u, minreq %u, fragsize %u",output_att->maxlength, output_att->tlength, LOG("Output buffer attributes maxlength %u, tlength %u, prebuf %u, "
output_att->prebuf, output_att->minreq, output_att->fragsize); "minreq %u, fragsize %u",
output_att->maxlength, output_att->tlength, output_att->prebuf,
output_att->minreq, output_att->fragsize);
} }
if (input_stream_params) { if (input_stream_params) {
const pa_buffer_attr * input_att; const pa_buffer_attr * input_att;
input_att = WRAP(pa_stream_get_buffer_attr)(stm->input_stream); input_att = WRAP(pa_stream_get_buffer_attr)(stm->input_stream);
LOG("Input buffer attributes maxlength %u, tlength %u, prebuf %u, minreq %u, fragsize %u",input_att->maxlength, input_att->tlength, LOG("Input buffer attributes maxlength %u, tlength %u, prebuf %u, minreq "
input_att->prebuf, input_att->minreq, input_att->fragsize); "%u, fragsize %u",
input_att->maxlength, input_att->tlength, input_att->prebuf,
input_att->minreq, input_att->fragsize);
} }
} }
@ -1010,7 +1052,8 @@ pulse_stream_destroy(cubeb_stream * stm)
if (stm->drain_timer) { if (stm->drain_timer) {
/* there's no pa_rttime_free, so use this instead. */ /* there's no pa_rttime_free, so use this instead. */
WRAP(pa_threaded_mainloop_get_api)(stm->context->mainloop)->time_free(stm->drain_timer); WRAP(pa_threaded_mainloop_get_api)
(stm->context->mainloop)->time_free(stm->drain_timer);
} }
WRAP(pa_stream_set_state_callback)(stm->output_stream, NULL, NULL); WRAP(pa_stream_set_state_callback)(stm->output_stream, NULL, NULL);
@ -1054,7 +1097,8 @@ pulse_stream_start(cubeb_stream * stm)
* things roll. This is done via a defer event in order to execute it * things roll. This is done via a defer event in order to execute it
* from PA server thread. */ * from PA server thread. */
WRAP(pa_threaded_mainloop_lock)(stm->context->mainloop); WRAP(pa_threaded_mainloop_lock)(stm->context->mainloop);
WRAP(pa_mainloop_api_once)(WRAP(pa_threaded_mainloop_get_api)(stm->context->mainloop), WRAP(pa_mainloop_api_once)
(WRAP(pa_threaded_mainloop_get_api)(stm->context->mainloop),
pulse_defer_event_cb, stm); pulse_defer_event_cb, stm);
WRAP(pa_threaded_mainloop_unlock)(stm->context->mainloop); WRAP(pa_threaded_mainloop_unlock)(stm->context->mainloop);
} }
@ -1178,9 +1222,8 @@ pulse_stream_set_volume(cubeb_stream * stm, float volume)
index = WRAP(pa_stream_get_index)(stm->output_stream); index = WRAP(pa_stream_get_index)(stm->output_stream);
op = WRAP(pa_context_set_sink_input_volume)(ctx->context, op = WRAP(pa_context_set_sink_input_volume)(ctx->context, index, &cvol,
index, &cvol, volume_success, volume_success, stm);
stm);
if (op) { if (op) {
operation_wait(ctx, stm->output_stream, op); operation_wait(ctx, stm->output_stream, op);
WRAP(pa_operation_unref)(op); WRAP(pa_operation_unref)(op);
@ -1201,8 +1244,8 @@ pulse_stream_set_name(cubeb_stream * stm, char const * stream_name)
WRAP(pa_threaded_mainloop_lock)(stm->context->mainloop); WRAP(pa_threaded_mainloop_lock)(stm->context->mainloop);
pa_operation * op = pa_operation * op = WRAP(pa_stream_set_name)(stm->output_stream, stream_name,
WRAP(pa_stream_set_name)(stm->output_stream, stream_name, rename_success, stm); rename_success, stm);
if (op) { if (op) {
operation_wait(stm->context, stm->output_stream, op); operation_wait(stm->context, stm->output_stream, op);
@ -1246,8 +1289,8 @@ pulse_ensure_dev_list_data_list_size (pulse_dev_list_data * list_data)
{ {
if (list_data->count == list_data->max) { if (list_data->count == list_data->max) {
list_data->max += 8; list_data->max += 8;
list_data->devinfo = realloc(list_data->devinfo, list_data->devinfo =
sizeof(cubeb_device_info) * list_data->max); realloc(list_data->devinfo, sizeof(cubeb_device_info) * list_data->max);
} }
} }
@ -1267,8 +1310,8 @@ pulse_get_state_from_sink_port(pa_sink_port_info * info)
} }
static void static void
pulse_sink_info_cb(pa_context * context, const pa_sink_info * info, pulse_sink_info_cb(pa_context * context, const pa_sink_info * info, int eol,
int eol, void * user_data) void * user_data)
{ {
pulse_dev_list_data * list_data = user_data; pulse_dev_list_data * list_data = user_data;
cubeb_device_info * devinfo; cubeb_device_info * devinfo;
@ -1307,11 +1350,13 @@ pulse_sink_info_cb(pa_context * context, const pa_sink_info * info,
devinfo->type = CUBEB_DEVICE_TYPE_OUTPUT; devinfo->type = CUBEB_DEVICE_TYPE_OUTPUT;
devinfo->state = pulse_get_state_from_sink_port(info->active_port); devinfo->state = pulse_get_state_from_sink_port(info->active_port);
devinfo->preferred = (strcmp(info->name, list_data->default_sink_name) == 0) ? devinfo->preferred = (strcmp(info->name, list_data->default_sink_name) == 0)
CUBEB_DEVICE_PREF_ALL : CUBEB_DEVICE_PREF_NONE; ? CUBEB_DEVICE_PREF_ALL
: CUBEB_DEVICE_PREF_NONE;
devinfo->format = CUBEB_DEVICE_FMT_ALL; devinfo->format = CUBEB_DEVICE_FMT_ALL;
devinfo->default_format = pulse_format_to_cubeb_format(info->sample_spec.format); devinfo->default_format =
pulse_format_to_cubeb_format(info->sample_spec.format);
devinfo->max_channels = info->channel_map.channels; devinfo->max_channels = info->channel_map.channels;
devinfo->min_rate = 1; devinfo->min_rate = 1;
devinfo->max_rate = PA_RATE_MAX; devinfo->max_rate = PA_RATE_MAX;
@ -1339,8 +1384,8 @@ pulse_get_state_from_source_port(pa_source_port_info * info)
} }
static void static void
pulse_source_info_cb(pa_context * context, const pa_source_info * info, pulse_source_info_cb(pa_context * context, const pa_source_info * info, int eol,
int eol, void * user_data) void * user_data)
{ {
pulse_dev_list_data * list_data = user_data; pulse_dev_list_data * list_data = user_data;
cubeb_device_info * devinfo; cubeb_device_info * devinfo;
@ -1376,11 +1421,13 @@ pulse_source_info_cb(pa_context * context, const pa_source_info * info,
devinfo->type = CUBEB_DEVICE_TYPE_INPUT; devinfo->type = CUBEB_DEVICE_TYPE_INPUT;
devinfo->state = pulse_get_state_from_source_port(info->active_port); devinfo->state = pulse_get_state_from_source_port(info->active_port);
devinfo->preferred = (strcmp(info->name, list_data->default_source_name) == 0) ? devinfo->preferred = (strcmp(info->name, list_data->default_source_name) == 0)
CUBEB_DEVICE_PREF_ALL : CUBEB_DEVICE_PREF_NONE; ? CUBEB_DEVICE_PREF_ALL
: CUBEB_DEVICE_PREF_NONE;
devinfo->format = CUBEB_DEVICE_FMT_ALL; devinfo->format = CUBEB_DEVICE_FMT_ALL;
devinfo->default_format = pulse_format_to_cubeb_format(info->sample_spec.format); devinfo->default_format =
pulse_format_to_cubeb_format(info->sample_spec.format);
devinfo->max_channels = info->channel_map.channels; devinfo->max_channels = info->channel_map.channels;
devinfo->min_rate = 1; devinfo->min_rate = 1;
devinfo->max_rate = PA_RATE_MAX; devinfo->max_rate = PA_RATE_MAX;
@ -1418,8 +1465,8 @@ pulse_enumerate_devices(cubeb * context, cubeb_device_type type,
WRAP(pa_threaded_mainloop_lock)(context->mainloop); WRAP(pa_threaded_mainloop_lock)(context->mainloop);
o = WRAP(pa_context_get_server_info)(context->context, o = WRAP(pa_context_get_server_info)(context->context, pulse_server_info_cb,
pulse_server_info_cb, &user_data); &user_data);
if (o) { if (o) {
operation_wait(context, NULL, o); operation_wait(context, NULL, o);
WRAP(pa_operation_unref)(o); WRAP(pa_operation_unref)(o);
@ -1454,7 +1501,8 @@ pulse_enumerate_devices(cubeb * context, cubeb_device_type type,
} }
static int static int
pulse_device_collection_destroy(cubeb * ctx, cubeb_device_collection * collection) pulse_device_collection_destroy(cubeb * ctx,
cubeb_device_collection * collection)
{ {
size_t n; size_t n;
@ -1469,7 +1517,8 @@ pulse_device_collection_destroy(cubeb * ctx, cubeb_device_collection * collectio
} }
static int static int
pulse_stream_get_current_device(cubeb_stream * stm, cubeb_device ** const device) pulse_stream_get_current_device(cubeb_stream * stm,
cubeb_device ** const device)
{ {
#if PA_CHECK_VERSION(0, 9, 8) #if PA_CHECK_VERSION(0, 9, 8)
*device = calloc(1, sizeof(cubeb_device)); *device = calloc(1, sizeof(cubeb_device));
@ -1493,8 +1542,7 @@ pulse_stream_get_current_device(cubeb_stream * stm, cubeb_device ** const device
} }
static int static int
pulse_stream_device_destroy(cubeb_stream * stream, pulse_stream_device_destroy(cubeb_stream * stream, cubeb_device * device)
cubeb_device * device)
{ {
(void)stream; (void)stream;
free(device->input_name); free(device->input_name);
@ -1504,8 +1552,7 @@ pulse_stream_device_destroy(cubeb_stream * stream,
} }
static void static void
pulse_subscribe_callback(pa_context * ctx, pulse_subscribe_callback(pa_context * ctx, pa_subscription_event_type_t t,
pa_subscription_event_type_t t,
uint32_t index, void * userdata) uint32_t index, void * userdata)
{ {
(void)ctx; (void)ctx;
@ -1515,36 +1562,49 @@ pulse_subscribe_callback(pa_context * ctx,
case PA_SUBSCRIPTION_EVENT_SERVER: case PA_SUBSCRIPTION_EVENT_SERVER:
if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_CHANGE) { if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_CHANGE) {
LOG("Server changed %d", index); LOG("Server changed %d", index);
WRAP(pa_context_get_server_info)(context->context, server_info_callback, context); WRAP(pa_context_get_server_info)
(context->context, server_info_callback, context);
} }
break; break;
case PA_SUBSCRIPTION_EVENT_SOURCE: case PA_SUBSCRIPTION_EVENT_SOURCE:
case PA_SUBSCRIPTION_EVENT_SINK: case PA_SUBSCRIPTION_EVENT_SINK:
if (g_cubeb_log_level) { if (g_cubeb_log_level) {
if ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SOURCE && if ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) ==
(t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) { PA_SUBSCRIPTION_EVENT_SOURCE &&
(t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) ==
PA_SUBSCRIPTION_EVENT_REMOVE) {
LOG("Removing source index %d", index); LOG("Removing source index %d", index);
} else if ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SOURCE && } else if ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) ==
(t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_NEW) { PA_SUBSCRIPTION_EVENT_SOURCE &&
(t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) ==
PA_SUBSCRIPTION_EVENT_NEW) {
LOG("Adding source index %d", index); LOG("Adding source index %d", index);
} }
if ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SINK && if ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) ==
(t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) { PA_SUBSCRIPTION_EVENT_SINK &&
(t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) ==
PA_SUBSCRIPTION_EVENT_REMOVE) {
LOG("Removing sink index %d", index); LOG("Removing sink index %d", index);
} else if ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SINK && } else if ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) ==
(t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_NEW) { PA_SUBSCRIPTION_EVENT_SINK &&
(t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) ==
PA_SUBSCRIPTION_EVENT_NEW) {
LOG("Adding sink index %d", index); LOG("Adding sink index %d", index);
} }
} }
if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE || if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE ||
(t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_NEW) { (t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_NEW) {
if ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SOURCE) { if ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) ==
context->input_collection_changed_callback(context, context->input_collection_changed_user_ptr); PA_SUBSCRIPTION_EVENT_SOURCE) {
context->input_collection_changed_callback(
context, context->input_collection_changed_user_ptr);
} }
if ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SINK) { if ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) ==
context->output_collection_changed_callback(context, context->output_collection_changed_user_ptr); PA_SUBSCRIPTION_EVENT_SINK) {
context->output_collection_changed_callback(
context, context->output_collection_changed_user_ptr);
} }
} }
break; break;
@ -1561,13 +1621,16 @@ subscribe_success(pa_context *c, int success, void *userdata)
} }
static int static int
pulse_subscribe_notifications(cubeb * context, pa_subscription_mask_t mask) { pulse_subscribe_notifications(cubeb * context, pa_subscription_mask_t mask)
{
WRAP(pa_threaded_mainloop_lock)(context->mainloop); WRAP(pa_threaded_mainloop_lock)(context->mainloop);
WRAP(pa_context_set_subscribe_callback)(context->context, pulse_subscribe_callback, context); WRAP(pa_context_set_subscribe_callback)
(context->context, pulse_subscribe_callback, context);
pa_operation * o; pa_operation * o;
o = WRAP(pa_context_subscribe)(context->context, mask, subscribe_success, context); o = WRAP(pa_context_subscribe)(context->context, mask, subscribe_success,
context);
if (o == NULL) { if (o == NULL) {
WRAP(pa_threaded_mainloop_unlock)(context->mainloop); WRAP(pa_threaded_mainloop_unlock)(context->mainloop);
LOG("Context subscribe failed"); LOG("Context subscribe failed");
@ -1582,8 +1645,8 @@ pulse_subscribe_notifications(cubeb * context, pa_subscription_mask_t mask) {
} }
static int static int
pulse_register_device_collection_changed(cubeb * context, pulse_register_device_collection_changed(
cubeb_device_type devtype, cubeb * context, cubeb_device_type devtype,
cubeb_device_collection_changed_callback collection_changed_callback, cubeb_device_collection_changed_callback collection_changed_callback,
void * user_ptr) void * user_ptr)
{ {
@ -1625,7 +1688,6 @@ static struct cubeb_ops const pulse_ops = {
.stream_destroy = pulse_stream_destroy, .stream_destroy = pulse_stream_destroy,
.stream_start = pulse_stream_start, .stream_start = pulse_stream_start,
.stream_stop = pulse_stream_stop, .stream_stop = pulse_stream_stop,
.stream_reset_default_device = NULL,
.stream_get_position = pulse_stream_get_position, .stream_get_position = pulse_stream_get_position,
.stream_get_latency = pulse_stream_get_latency, .stream_get_latency = pulse_stream_get_latency,
.stream_get_input_latency = NULL, .stream_get_input_latency = NULL,
@ -1634,5 +1696,5 @@ static struct cubeb_ops const pulse_ops = {
.stream_get_current_device = pulse_stream_get_current_device, .stream_get_current_device = pulse_stream_get_current_device,
.stream_device_destroy = pulse_stream_device_destroy, .stream_device_destroy = pulse_stream_device_destroy,
.stream_register_device_changed_callback = NULL, .stream_register_device_changed_callback = NULL,
.register_device_collection_changed = pulse_register_device_collection_changed .register_device_collection_changed =
}; pulse_register_device_collection_changed};

View file

@ -8,16 +8,16 @@
#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)
@ -35,7 +35,8 @@ 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;
} }
@ -46,23 +47,22 @@ passthrough_resampler<T>::passthrough_resampler(cubeb_stream * s,
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
passthrough_resampler<T>::fill(void * input_buffer, long * input_frames_count,
void * output_buffer, long output_frames) 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
@ -79,7 +79,8 @@ long passthrough_resampler<T>::fill(void * input_buffer, long * input_frames_cou
// 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
// happens.
internal_input_buffer.push(static_cast<T *>(input_buffer), 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)) {
@ -87,8 +88,8 @@ long passthrough_resampler<T>::fill(void * input_buffer, long * input_frames_cou
// 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);
} }
@ -100,12 +101,14 @@ long passthrough_resampler<T>::fill(void * input_buffer, long * input_frames_cou
// 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(
static_cast<T *>(input_buffer) + samples_off,
frames_to_samples(*input_frames_count - output_frames)); 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) {
@ -125,17 +128,12 @@ 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;
@ -147,28 +145,29 @@ cubeb_resampler_speex<T, InputProcessor, OutputProcessor>
} }
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);
@ -185,8 +184,7 @@ cubeb_resampler_speex<T, InputProcessor, OutputProcessor>
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) {
@ -207,27 +205,36 @@ cubeb_resampler_speex<T, InputProcessor, OutputProcessor>
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
@ -237,16 +244,17 @@ cubeb_resampler_speex<T, InputProcessor, OutputProcessor>
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;
@ -277,15 +285,14 @@ cubeb_resampler_speex<T, InputProcessor, OutputProcessor>
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;
@ -332,21 +337,13 @@ cubeb_resampler_create(cubeb_stream * stream,
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,
callback,
user_ptr,
quality);
case CUBEB_SAMPLE_FLOAT32NE: case CUBEB_SAMPLE_FLOAT32NE:
return cubeb_resampler_create_internal<float>(stream, return cubeb_resampler_create_internal<float>(stream, input_params,
input_params, output_params, target_rate,
output_params, callback, user_ptr, quality);
target_rate,
callback,
user_ptr,
quality);
default: default:
assert(false); assert(false);
return nullptr; return nullptr;
@ -354,14 +351,12 @@ cubeb_resampler_create(cubeb_stream * stream,
} }
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_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);
/** /**
* 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"
@ -31,23 +31,26 @@ MOZ_BEGIN_STD_NAMESPACE
#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));
@ -77,29 +76,23 @@ protected:
}; };
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));
} }
} }
@ -120,10 +113,8 @@ 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,10 +188,7 @@ 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,
&input_frame_count,
output_buffer,
&output_frame_count); &output_frame_count);
} }
@ -227,8 +212,8 @@ public:
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,6 +328,7 @@ 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. */
@ -347,10 +339,8 @@ 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_buffer,
output_frame_count); output_frame_count);
assert(rv == RESAMPLER_ERR_SUCCESS); assert(rv == RESAMPLER_ERR_SUCCESS);
} }
@ -362,10 +352,8 @@ 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_buffer,
output_frame_count); output_frame_count);
assert(rv == RESAMPLER_ERR_SUCCESS); assert(rv == RESAMPLER_ERR_SUCCESS);
} }
@ -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;
@ -512,8 +495,7 @@ 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;
@ -530,21 +512,19 @@ cubeb_resampler_create_internal(cubeb_stream * stream,
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,
output_params->rate,
to_speex_quality(quality))); to_speex_quality(quality)));
if (!output_resampler) { if (!output_resampler) {
return NULL; return NULL;
@ -552,10 +532,8 @@ cubeb_resampler_create_internal(cubeb_stream * stream,
} }
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,
target_rate,
to_speex_quality(quality))); 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
space for the buffers. */
unsigned int tail; /**< Index of the last element (first to deliver). */ unsigned int tail; /**< Index of the last element (first to deliver). */
unsigned int count; /**< Number of elements in the array. */ unsigned int count; /**< Number of elements in the array. */
unsigned int capacity; /**< Total length of 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);
@ -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;
@ -100,7 +95,8 @@ ring_array_destroy(ring_array * ra)
/** 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)
{ {

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.
@ -64,8 +62,7 @@ public:
/* 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();
@ -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,8 +456,7 @@ 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),

View file

@ -4,29 +4,31 @@
* 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) \
@ -41,7 +43,7 @@
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);
@ -319,7 +321,8 @@ sndio_init(cubeb **context, char const *context_name)
} }
} }
#define LOAD(x) { \ #define LOAD(x) \
{ \
cubeb_##x = dlsym(libsndio, #x); \ cubeb_##x = dlsym(libsndio, #x); \
if (!cubeb_##x) { \ if (!cubeb_##x) { \
DPR("sndio_init(%s) failed dlsym(%s)\n", context_name, #x); \ DPR("sndio_init(%s) failed dlsym(%s)\n", context_name, #x); \
@ -365,17 +368,14 @@ 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;
@ -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.
@ -626,7 +627,7 @@ sndio_enumerate_devices(cubeb *context, cubeb_device_type type,
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;
@ -658,7 +659,6 @@ static struct cubeb_ops const sndio_ops = {
.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,
@ -666,5 +666,4 @@ static struct cubeb_ops const sndio_ops = {
.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

@ -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
@ -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;
@ -183,7 +183,8 @@ 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
@ -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;
@ -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,17 +509,13 @@ 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;
@ -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;
} }
@ -718,7 +722,6 @@ static struct cubeb_ops const sun_ops = {
.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,
@ -727,5 +730,4 @@ static struct cubeb_ops const sun_ops = {
.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,7 +7,8 @@
#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:

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"
@ -25,7 +25,8 @@
/** 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);
@ -34,7 +35,8 @@ 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);
@ -43,7 +45,8 @@ 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);
@ -52,7 +55,8 @@ void PodZero(T * destination, size_t count)
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];
@ -60,11 +64,12 @@ void Copy(T * destination, const T * source, size_t count, Trait)
} }
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
@ -72,7 +77,8 @@ void Copy(T * destination, const T * source, size_t count, std::true_type)
* 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());
@ -80,7 +86,8 @@ void Copy(T * destination, const T * source, size_t count)
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();
@ -88,50 +95,39 @@ void ConstructDefault(T * destination, size_t count, Trait)
} }
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
{ {
@ -146,22 +142,13 @@ public:
} }
/** 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.
@ -227,10 +214,7 @@ 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.
@ -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()
{ {

View file

@ -8,13 +8,13 @@
#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
@ -24,10 +24,7 @@ public:
InitializeCriticalSection(&critical_section); InitializeCriticalSection(&critical_section);
} }
~owned_critical_section() ~owned_critical_section() { DeleteCriticalSection(&critical_section); }
{
DeleteCriticalSection(&critical_section);
}
void lock() void lock()
{ {

File diff suppressed because it is too large Load diff

View file

@ -8,23 +8,28 @@
#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
@ -68,10 +73,16 @@
#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
@ -223,8 +238,7 @@ 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);
@ -256,7 +270,8 @@ 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;
@ -265,7 +280,8 @@ winmm_buffer_callback(HWAVEOUT waveout, UINT msg, DWORD_PTR user_ptr, DWORD_PTR
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);
@ -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;
} }
@ -827,12 +932,14 @@ winmm_query_preferred_out_device(UINT devid)
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,7 +958,8 @@ 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;
@ -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,7 +983,8 @@ 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;
@ -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;
@ -905,12 +1014,14 @@ winmm_query_preferred_in_device(UINT devid)
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,7 +1029,8 @@ 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;
@ -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,7 +1054,8 @@ 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;
@ -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) {
@ -1056,7 +1171,6 @@ static struct cubeb_ops const winmm_ops = {
/*.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,
@ -1065,5 +1179,4 @@ static struct cubeb_ops const winmm_ops = {
/*.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());