Add cubeb@62871b2 to Externals/
Includes submodule sanitizers-cmake@f09151b
This commit is contained in:
parent
d4faa099d9
commit
812520cc65
|
@ -660,6 +660,8 @@ find_package(OpenAL)
|
||||||
add_subdirectory(Externals/soundtouch)
|
add_subdirectory(Externals/soundtouch)
|
||||||
include_directories(Externals)
|
include_directories(Externals)
|
||||||
|
|
||||||
|
add_subdirectory(Externals/cubeb EXCLUDE_FROM_ALL)
|
||||||
|
|
||||||
if(NOT ANDROID)
|
if(NOT ANDROID)
|
||||||
add_definitions(-D__LIBUSB__)
|
add_definitions(-D__LIBUSB__)
|
||||||
if(NOT APPLE)
|
if(NOT APPLE)
|
||||||
|
|
|
@ -0,0 +1,16 @@
|
||||||
|
Matthew Gregan <kinetik@flim.org>
|
||||||
|
Alexandre Ratchov <alex@caoua.org>
|
||||||
|
Michael Wu <mwu@mozilla.com>
|
||||||
|
Paul Adenot <paul@paul.cx>
|
||||||
|
David Richards <drichards@mozilla.com>
|
||||||
|
Sebastien Alaiwan <sebastien.alaiwan@gmail.com>
|
||||||
|
KO Myung-Hun <komh@chollian.net>
|
||||||
|
Haakon Sporsheim <haakon.sporsheim@telenordigital.com>
|
||||||
|
Alex Chronopoulos <achronop@gmail.com>
|
||||||
|
Jan Beich <jbeich@FreeBSD.org>
|
||||||
|
Vito Caputo <vito.caputo@coreos.com>
|
||||||
|
Landry Breuil <landry@openbsd.org>
|
||||||
|
Jacek Caban <jacek@codeweavers.com>
|
||||||
|
Paul Hancock <Paul.Hancock.17041993@live.com>
|
||||||
|
Ted Mielczarek <ted@mielczarek.org>
|
||||||
|
Chun-Min Chang <chun.m.chang@gmail.com>
|
|
@ -0,0 +1,150 @@
|
||||||
|
# TODO
|
||||||
|
# - backend selection via command line, rather than simply detecting headers.
|
||||||
|
|
||||||
|
cmake_minimum_required(VERSION 3.1 FATAL_ERROR)
|
||||||
|
project(cubeb
|
||||||
|
VERSION 0.0.0)
|
||||||
|
|
||||||
|
option(BUILD_SHARED_LIBS "Build shared libraries" OFF)
|
||||||
|
|
||||||
|
if(NOT CMAKE_BUILD_TYPE)
|
||||||
|
set(CMAKE_BUILD_TYPE "RelWithDebInfo" CACHE STRING
|
||||||
|
"Choose the type of build, options are: Debug Release RelWithDebInfo MinSizeRel." FORCE)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(POLICY CMP0063)
|
||||||
|
cmake_policy(SET CMP0063 NEW)
|
||||||
|
endif()
|
||||||
|
set(CMAKE_C_STANDARD 99)
|
||||||
|
set(CMAKE_CXX_STANDARD 11)
|
||||||
|
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||||
|
|
||||||
|
if(NOT COMMAND add_sanitizers)
|
||||||
|
list(APPEND CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake/sanitizers-cmake/cmake")
|
||||||
|
find_package(Sanitizers)
|
||||||
|
if(NOT COMMAND add_sanitizers)
|
||||||
|
message(FATAL_ERROR "Could not find sanitizers-cmake: run\n\tgit submodule update --init --recursive\nin base git checkout")
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
|
||||||
|
set(CMAKE_C_VISIBILITY_PRESET hidden)
|
||||||
|
set(CMAKE_CXX_VISIBILITY_PRESET hidden)
|
||||||
|
set(CMAKE_VISIBILITY_INLINES_HIDDEN 1)
|
||||||
|
|
||||||
|
set(CMAKE_CXX_WARNING_LEVEL 4)
|
||||||
|
if(NOT MSVC)
|
||||||
|
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra -Wno-unused-parameter")
|
||||||
|
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Wno-unused-parameter")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
add_library(cubeb
|
||||||
|
src/cubeb.c
|
||||||
|
src/cubeb_mixer.cpp
|
||||||
|
src/cubeb_resampler.cpp
|
||||||
|
src/cubeb_panner.cpp
|
||||||
|
src/cubeb_log.cpp
|
||||||
|
src/cubeb_utils.c
|
||||||
|
$<TARGET_OBJECTS:speex>)
|
||||||
|
target_include_directories(cubeb PUBLIC include)
|
||||||
|
target_include_directories(cubeb PRIVATE src)
|
||||||
|
target_compile_definitions(cubeb PRIVATE OUTSIDE_SPEEX)
|
||||||
|
target_compile_definitions(cubeb PRIVATE FLOATING_POINT)
|
||||||
|
target_compile_definitions(cubeb PRIVATE EXPORT=)
|
||||||
|
target_compile_definitions(cubeb PRIVATE RANDOM_PREFIX=speex)
|
||||||
|
|
||||||
|
add_sanitizers(cubeb)
|
||||||
|
|
||||||
|
include(GenerateExportHeader)
|
||||||
|
generate_export_header(cubeb EXPORT_FILE_NAME ${CMAKE_BINARY_DIR}/exports/cubeb_export.h)
|
||||||
|
target_include_directories(cubeb PUBLIC ${CMAKE_BINARY_DIR}/exports)
|
||||||
|
|
||||||
|
add_library(speex OBJECT
|
||||||
|
src/speex/resample.c)
|
||||||
|
set_target_properties(speex PROPERTIES POSITION_INDEPENDENT_CODE TRUE)
|
||||||
|
target_compile_definitions(speex PRIVATE OUTSIDE_SPEEX)
|
||||||
|
target_compile_definitions(speex PRIVATE FLOATING_POINT)
|
||||||
|
target_compile_definitions(speex PRIVATE EXPORT=)
|
||||||
|
target_compile_definitions(speex PRIVATE RANDOM_PREFIX=speex)
|
||||||
|
|
||||||
|
include(CheckIncludeFiles)
|
||||||
|
|
||||||
|
check_include_files(AudioUnit/AudioUnit.h USE_AUDIOUNIT)
|
||||||
|
if(USE_AUDIOUNIT)
|
||||||
|
target_sources(cubeb PRIVATE
|
||||||
|
src/cubeb_audiounit.cpp
|
||||||
|
src/cubeb_osx_run_loop.cpp)
|
||||||
|
target_compile_definitions(cubeb PRIVATE USE_AUDIOUNIT)
|
||||||
|
target_link_libraries(cubeb PRIVATE "-framework AudioUnit" "-framework CoreAudio" "-framework CoreServices")
|
||||||
|
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 dl)
|
||||||
|
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 asound pthread)
|
||||||
|
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 dl pthread)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
check_include_files(audioclient.h USE_WASAPI)
|
||||||
|
if(USE_WASAPI)
|
||||||
|
target_sources(cubeb PRIVATE
|
||||||
|
src/cubeb_wasapi.cpp)
|
||||||
|
target_compile_definitions(cubeb PRIVATE USE_WASAPI)
|
||||||
|
target_link_libraries(cubeb PRIVATE avrt)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
check_include_files("windows.h;mmsystem.h" USE_WINMM)
|
||||||
|
if(USE_WINMM)
|
||||||
|
target_sources(cubeb PRIVATE
|
||||||
|
src/cubeb_winmm.c)
|
||||||
|
target_compile_definitions(cubeb PRIVATE USE_WINMM)
|
||||||
|
target_link_libraries(cubeb PRIVATE winmm)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
check_include_files(SLES/OpenSLES.h USE_OPENSL)
|
||||||
|
if(USE_OPENSL)
|
||||||
|
target_sources(cubeb PRIVATE
|
||||||
|
src/cubeb_opensl.c)
|
||||||
|
target_compile_definitions(cubeb PRIVATE USE_OPENSL)
|
||||||
|
target_link_libraries(cubeb PRIVATE OpenSLES)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
check_include_files(android/log.h USE_AUDIOTRACK)
|
||||||
|
if(USE_AUDIOTRACK)
|
||||||
|
target_sources(cubeb PRIVATE
|
||||||
|
src/cubeb_audiotrack.c)
|
||||||
|
target_compile_definitions(cubeb PRIVATE USE_AUDIOTRACK)
|
||||||
|
target_link_libraries(cubeb PRIVATE log)
|
||||||
|
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 sndio)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
check_include_files(kai.h USE_KAI)
|
||||||
|
if(USE_KAI)
|
||||||
|
target_sources(cubeb PRIVATE
|
||||||
|
src/cubeb_kai.c)
|
||||||
|
target_compile_definitions(cubeb PRIVATE USE_KAI)
|
||||||
|
target_link_libraries(cubeb PRIVATE kai)
|
||||||
|
endif()
|
|
@ -0,0 +1,24 @@
|
||||||
|
# Build instructions for libcubeb
|
||||||
|
|
||||||
|
You must have CMake v3.1 or later installed.
|
||||||
|
|
||||||
|
1. `git clone --recursive https://github.com/kinetiknz/cubeb.git`
|
||||||
|
2. `mkdir cubeb-build`
|
||||||
|
3. `cd cubeb-build`
|
||||||
|
3. `cmake ../cubeb`
|
||||||
|
4. `cmake --build .`
|
||||||
|
5. `ctest`
|
||||||
|
|
||||||
|
# Windows build notes
|
||||||
|
|
||||||
|
Windows builds can use Microsoft Visual Studio 2015 (the default) or MinGW-w64
|
||||||
|
with Win32 threads (by passing `cmake -G` to generate the appropriate build
|
||||||
|
configuration). To build with MinGW-w64, install the following items:
|
||||||
|
|
||||||
|
- Download and install MinGW-w64 with Win32 threads.
|
||||||
|
- Download and install CMake.
|
||||||
|
- Run MinGW-w64 Terminal from the Start Menu.
|
||||||
|
- Follow the build steps above, but at step 3 run:
|
||||||
|
`cmake -G "MinGW Makefiles" ..`
|
||||||
|
- Continue the build steps above.
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
Copyright © 2011 Mozilla Foundation
|
||||||
|
|
||||||
|
Permission to use, copy, modify, and distribute this software for any
|
||||||
|
purpose with or without fee is hereby granted, provided that the above
|
||||||
|
copyright notice and this permission notice appear in all copies.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
@ -0,0 +1,6 @@
|
||||||
|
[![Build Status](https://travis-ci.org/kinetiknz/cubeb.svg?branch=master)](https://travis-ci.org/kinetiknz/cubeb)
|
||||||
|
[![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.
|
||||||
|
|
||||||
|
Licensed under an ISC-style license. See LICENSE for details.
|
|
@ -0,0 +1,22 @@
|
||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c)
|
||||||
|
2013 Matthew Arsenault
|
||||||
|
2015-2016 RWTH Aachen University, Federal Republic of Germany
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
this software and associated documentation files (the "Software"), to deal in
|
||||||
|
the Software without restriction, including without limitation the rights to
|
||||||
|
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
@ -0,0 +1,73 @@
|
||||||
|
# sanitizers-cmake
|
||||||
|
|
||||||
|
[![](https://img.shields.io/github/issues-raw/arsenm/sanitizers-cmake.svg?style=flat-square)](https://github.com/arsenm/sanitizers-cmake/issues)
|
||||||
|
[![MIT](http://img.shields.io/badge/license-MIT-blue.svg?style=flat-square)](LICENSE)
|
||||||
|
|
||||||
|
CMake module to enable sanitizers for binary targets.
|
||||||
|
|
||||||
|
|
||||||
|
## Include into your project
|
||||||
|
|
||||||
|
To use [FindSanitizers.cmake](cmake/FindSanitizers.cmake), simply add this repository as git submodule into your own repository
|
||||||
|
```Shell
|
||||||
|
mkdir externals
|
||||||
|
git submodule add git://github.com/arsenm/sanitizers-cmake.git externals/sanitizers-cmake
|
||||||
|
```
|
||||||
|
and adding ```externals/sanitizers-cmake/cmake``` to your ```CMAKE_MODULE_PATH```
|
||||||
|
```CMake
|
||||||
|
set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/externals/sanitizers-cmake/cmake" ${CMAKE_MODULE_PATH})
|
||||||
|
```
|
||||||
|
|
||||||
|
If you don't use git or dislike submodules you can copy the files in [cmake directory](cmake) into your repository. *Be careful and keep updates in mind!*
|
||||||
|
|
||||||
|
Now you can simply run ```find_package``` in your CMake files:
|
||||||
|
```CMake
|
||||||
|
find_package(Sanitizers)
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
You can enable the sanitizers with ``SANITIZE_ADDRESS``, ``SANITIZE_MEMORY``, ``SANITIZE_THREAD`` or ``SANITIZE_UNDEFINED`` options in your CMake configuration. You can do this by passing e.g. ``-DSANITIZE_ADDRESS=On`` on your command line or with your graphical interface.
|
||||||
|
|
||||||
|
If sanitizers are supported by your compiler, the specified targets will be build with sanitizer support. If your compiler has no sanitizing capabilities (I asume intel compiler doesn't) you'll get a warning but CMake will continue processing and sanitizing will simply just be ignored.
|
||||||
|
|
||||||
|
#### Compiler issues
|
||||||
|
|
||||||
|
Different compilers may be using different implementations for sanitizers. If you'll try to sanitize targets with C and Fortran code but don't use gcc & gfortran but clang & gfortran, this will cause linking problems. To avoid this, such problems will be detected and sanitizing will be disabled for these targets.
|
||||||
|
|
||||||
|
Even C only targets may cause problems in certain situations. Some problems have been seen with AddressSanitizer for preloading or dynamic linking. In such cases you may try the ``SANITIZE_LINK_STATIC`` to link sanitizers for gcc static.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Build targets with sanitizer support
|
||||||
|
|
||||||
|
To enable sanitizer support you simply have to add ``add_sanitizers(<TARGET>)`` after defining your target. To provide a sanitizer blacklist file you can use the ``add_sanitizer_blacklist(<FILE>)`` function:
|
||||||
|
```CMake
|
||||||
|
find_package(Sanitizers)
|
||||||
|
|
||||||
|
add_sanitizer_blacklist("blacklist.txt")
|
||||||
|
|
||||||
|
add_executable(some_exe foo.c bar.c)
|
||||||
|
add_sanitizers(some_exe)
|
||||||
|
|
||||||
|
add_library(some_lib foo.c bar.c)
|
||||||
|
add_sanitizers(some_lib)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Run your application
|
||||||
|
|
||||||
|
The sanitizers check your program, while it's running. In some situations (e.g. LD_PRELOAD your target) it might be required to preload the used AddressSanitizer library first. In this case you may use the ``asan-wrapper`` script defined in ``ASan_WRAPPER`` variable to execute your application with ``${ASan_WRAPPER} myexe arg1 ...``.
|
||||||
|
|
||||||
|
|
||||||
|
## Contribute
|
||||||
|
|
||||||
|
Anyone is welcome to contribute. Simply fork this repository, make your changes **in an own branch** and create a pull-request for your change. Please do only one change per pull-request.
|
||||||
|
|
||||||
|
You found a bug? Please fill out an [issue](https://github.com/arsenm/sanitizers-cmake/issues) and include any data to reproduce the bug.
|
||||||
|
|
||||||
|
|
||||||
|
#### Contributors
|
||||||
|
|
||||||
|
* [Matt Arsenault](https://github.com/arsenm)
|
||||||
|
* [Alexander Haase](https://github.com/alehaa)
|
|
@ -0,0 +1,59 @@
|
||||||
|
# The MIT License (MIT)
|
||||||
|
#
|
||||||
|
# Copyright (c)
|
||||||
|
# 2013 Matthew Arsenault
|
||||||
|
# 2015-2016 RWTH Aachen University, Federal Republic of Germany
|
||||||
|
#
|
||||||
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
# of this software and associated documentation files (the "Software"), to deal
|
||||||
|
# in the Software without restriction, including without limitation the rights
|
||||||
|
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
# copies of the Software, and to permit persons to whom the Software is
|
||||||
|
# furnished to do so, subject to the following conditions:
|
||||||
|
#
|
||||||
|
# The above copyright notice and this permission notice shall be included in all
|
||||||
|
# copies or substantial portions of the Software.
|
||||||
|
#
|
||||||
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
# SOFTWARE.
|
||||||
|
|
||||||
|
option(SANITIZE_ADDRESS "Enable AddressSanitizer for sanitized targets." Off)
|
||||||
|
|
||||||
|
set(FLAG_CANDIDATES
|
||||||
|
# Clang 3.2+ use this version. The no-omit-frame-pointer option is optional.
|
||||||
|
"-g -fsanitize=address -fno-omit-frame-pointer"
|
||||||
|
"-g -fsanitize=address"
|
||||||
|
|
||||||
|
# Older deprecated flag for ASan
|
||||||
|
"-g -faddress-sanitizer"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
if (SANITIZE_ADDRESS AND (SANITIZE_THREAD OR SANITIZE_MEMORY))
|
||||||
|
message(FATAL_ERROR "AddressSanitizer is not compatible with "
|
||||||
|
"ThreadSanitizer or MemorySanitizer.")
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
|
||||||
|
include(sanitize-helpers)
|
||||||
|
|
||||||
|
if (SANITIZE_ADDRESS)
|
||||||
|
sanitizer_check_compiler_flags("${FLAG_CANDIDATES}" "AddressSanitizer"
|
||||||
|
"ASan")
|
||||||
|
|
||||||
|
find_program(ASan_WRAPPER "asan-wrapper" PATHS ${CMAKE_MODULE_PATH})
|
||||||
|
mark_as_advanced(ASan_WRAPPER)
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
function (add_sanitize_address TARGET)
|
||||||
|
if (NOT SANITIZE_ADDRESS)
|
||||||
|
return()
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
saitizer_add_flags(${TARGET} "AddressSanitizer" "ASan")
|
||||||
|
endfunction ()
|
|
@ -0,0 +1,57 @@
|
||||||
|
# The MIT License (MIT)
|
||||||
|
#
|
||||||
|
# Copyright (c)
|
||||||
|
# 2013 Matthew Arsenault
|
||||||
|
# 2015-2016 RWTH Aachen University, Federal Republic of Germany
|
||||||
|
#
|
||||||
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
# of this software and associated documentation files (the "Software"), to deal
|
||||||
|
# in the Software without restriction, including without limitation the rights
|
||||||
|
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
# copies of the Software, and to permit persons to whom the Software is
|
||||||
|
# furnished to do so, subject to the following conditions:
|
||||||
|
#
|
||||||
|
# The above copyright notice and this permission notice shall be included in all
|
||||||
|
# copies or substantial portions of the Software.
|
||||||
|
#
|
||||||
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
# SOFTWARE.
|
||||||
|
|
||||||
|
option(SANITIZE_MEMORY "Enable MemorySanitizer for sanitized targets." Off)
|
||||||
|
|
||||||
|
set(FLAG_CANDIDATES
|
||||||
|
"-g -fsanitize=memory"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
include(sanitize-helpers)
|
||||||
|
|
||||||
|
if (SANITIZE_MEMORY)
|
||||||
|
if (NOT ${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
|
||||||
|
message(WARNING "MemorySanitizer disabled for target ${TARGET} because "
|
||||||
|
"MemorySanitizer is supported for Linux systems only.")
|
||||||
|
set(SANITIZE_MEMORY Off CACHE BOOL
|
||||||
|
"Enable MemorySanitizer for sanitized targets." FORCE)
|
||||||
|
elseif (NOT ${CMAKE_SIZEOF_VOID_P} EQUAL 8)
|
||||||
|
message(WARNING "MemorySanitizer disabled for target ${TARGET} because "
|
||||||
|
"MemorySanitizer is supported for 64bit systems only.")
|
||||||
|
set(SANITIZE_MEMORY Off CACHE BOOL
|
||||||
|
"Enable MemorySanitizer for sanitized targets." FORCE)
|
||||||
|
else ()
|
||||||
|
sanitizer_check_compiler_flags("${FLAG_CANDIDATES}" "MemorySanitizer"
|
||||||
|
"MSan")
|
||||||
|
endif ()
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
function (add_sanitize_memory TARGET)
|
||||||
|
if (NOT SANITIZE_MEMORY)
|
||||||
|
return()
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
saitizer_add_flags(${TARGET} "MemorySanitizer" "MSan")
|
||||||
|
endfunction ()
|
|
@ -0,0 +1,87 @@
|
||||||
|
# The MIT License (MIT)
|
||||||
|
#
|
||||||
|
# Copyright (c)
|
||||||
|
# 2013 Matthew Arsenault
|
||||||
|
# 2015-2016 RWTH Aachen University, Federal Republic of Germany
|
||||||
|
#
|
||||||
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
# of this software and associated documentation files (the "Software"), to deal
|
||||||
|
# in the Software without restriction, including without limitation the rights
|
||||||
|
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
# copies of the Software, and to permit persons to whom the Software is
|
||||||
|
# furnished to do so, subject to the following conditions:
|
||||||
|
#
|
||||||
|
# The above copyright notice and this permission notice shall be included in all
|
||||||
|
# copies or substantial portions of the Software.
|
||||||
|
#
|
||||||
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
# SOFTWARE.
|
||||||
|
|
||||||
|
# If any of the used compiler is a GNU compiler, add a second option to static
|
||||||
|
# link against the sanitizers.
|
||||||
|
option(SANITIZE_LINK_STATIC "Try to link static against sanitizers." Off)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
set(FIND_QUIETLY_FLAG "")
|
||||||
|
if (DEFINED Sanitizers_FIND_QUIETLY)
|
||||||
|
set(FIND_QUIETLY_FLAG "QUIET")
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
find_package(ASan ${FIND_QUIETLY_FLAG})
|
||||||
|
find_package(TSan ${FIND_QUIETLY_FLAG})
|
||||||
|
find_package(MSan ${FIND_QUIETLY_FLAG})
|
||||||
|
find_package(UBSan ${FIND_QUIETLY_FLAG})
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
function(sanitizer_add_blacklist_file FILE)
|
||||||
|
if(NOT IS_ABSOLUTE ${FILE})
|
||||||
|
set(FILE "${CMAKE_CURRENT_SOURCE_DIR}/${FILE}")
|
||||||
|
endif()
|
||||||
|
get_filename_component(FILE "${FILE}" REALPATH)
|
||||||
|
|
||||||
|
sanitizer_check_compiler_flags("-fsanitize-blacklist=${FILE}"
|
||||||
|
"SanitizerBlacklist" "SanBlist")
|
||||||
|
endfunction()
|
||||||
|
|
||||||
|
function(add_sanitizers ...)
|
||||||
|
# If no sanitizer is enabled, return immediately.
|
||||||
|
if (NOT (SANITIZE_ADDRESS OR SANITIZE_MEMORY OR SANITIZE_THREAD OR
|
||||||
|
SANITIZE_UNDEFINED))
|
||||||
|
return()
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
foreach (TARGET ${ARGV})
|
||||||
|
# Check if this target will be compiled by exactly one compiler. Other-
|
||||||
|
# wise sanitizers can't be used and a warning should be printed once.
|
||||||
|
sanitizer_target_compilers(${TARGET} TARGET_COMPILER)
|
||||||
|
list(LENGTH TARGET_COMPILER NUM_COMPILERS)
|
||||||
|
if (NUM_COMPILERS GREATER 1)
|
||||||
|
message(WARNING "Can't use any sanitizers for target ${TARGET}, "
|
||||||
|
"because it will be compiled by incompatible compilers. "
|
||||||
|
"Target will be compiled without sanitzers.")
|
||||||
|
return()
|
||||||
|
|
||||||
|
# If the target is compiled by no known compiler, ignore it.
|
||||||
|
elseif (NUM_COMPILERS EQUAL 0)
|
||||||
|
message(WARNING "Can't use any sanitizers for target ${TARGET}, "
|
||||||
|
"because it uses an unknown compiler. Target will be "
|
||||||
|
"compiled without sanitzers.")
|
||||||
|
return()
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
# Add sanitizers for target.
|
||||||
|
add_sanitize_address(${TARGET})
|
||||||
|
add_sanitize_thread(${TARGET})
|
||||||
|
add_sanitize_memory(${TARGET})
|
||||||
|
add_sanitize_undefined(${TARGET})
|
||||||
|
endforeach ()
|
||||||
|
endfunction(add_sanitizers)
|
|
@ -0,0 +1,64 @@
|
||||||
|
# The MIT License (MIT)
|
||||||
|
#
|
||||||
|
# Copyright (c)
|
||||||
|
# 2013 Matthew Arsenault
|
||||||
|
# 2015-2016 RWTH Aachen University, Federal Republic of Germany
|
||||||
|
#
|
||||||
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
# of this software and associated documentation files (the "Software"), to deal
|
||||||
|
# in the Software without restriction, including without limitation the rights
|
||||||
|
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
# copies of the Software, and to permit persons to whom the Software is
|
||||||
|
# furnished to do so, subject to the following conditions:
|
||||||
|
#
|
||||||
|
# The above copyright notice and this permission notice shall be included in all
|
||||||
|
# copies or substantial portions of the Software.
|
||||||
|
#
|
||||||
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
# SOFTWARE.
|
||||||
|
|
||||||
|
option(SANITIZE_THREAD "Enable ThreadSanitizer for sanitized targets." Off)
|
||||||
|
|
||||||
|
set(FLAG_CANDIDATES
|
||||||
|
"-g -fsanitize=thread"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# ThreadSanitizer is not compatible with MemorySanitizer.
|
||||||
|
if (SANITIZE_THREAD AND SANITIZE_MEMORY)
|
||||||
|
message(FATAL_ERROR "ThreadSanitizer is not compatible with "
|
||||||
|
"MemorySanitizer.")
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
|
||||||
|
include(sanitize-helpers)
|
||||||
|
|
||||||
|
if (SANITIZE_THREAD)
|
||||||
|
if (NOT ${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
|
||||||
|
message(WARNING "ThreadSanitizer disabled for target ${TARGET} because "
|
||||||
|
"ThreadSanitizer is supported for Linux systems only.")
|
||||||
|
set(SANITIZE_THREAD Off CACHE BOOL
|
||||||
|
"Enable ThreadSanitizer for sanitized targets." FORCE)
|
||||||
|
elseif (NOT ${CMAKE_SIZEOF_VOID_P} EQUAL 8)
|
||||||
|
message(WARNING "ThreadSanitizer disabled for target ${TARGET} because "
|
||||||
|
"ThreadSanitizer is supported for 64bit systems only.")
|
||||||
|
set(SANITIZE_THREAD Off CACHE BOOL
|
||||||
|
"Enable ThreadSanitizer for sanitized targets." FORCE)
|
||||||
|
else ()
|
||||||
|
sanitizer_check_compiler_flags("${FLAG_CANDIDATES}" "ThreadSanitizer"
|
||||||
|
"TSan")
|
||||||
|
endif ()
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
function (add_sanitize_thread TARGET)
|
||||||
|
if (NOT SANITIZE_THREAD)
|
||||||
|
return()
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
saitizer_add_flags(${TARGET} "ThreadSanitizer" "TSan")
|
||||||
|
endfunction ()
|
|
@ -0,0 +1,46 @@
|
||||||
|
# The MIT License (MIT)
|
||||||
|
#
|
||||||
|
# Copyright (c)
|
||||||
|
# 2013 Matthew Arsenault
|
||||||
|
# 2015-2016 RWTH Aachen University, Federal Republic of Germany
|
||||||
|
#
|
||||||
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
# of this software and associated documentation files (the "Software"), to deal
|
||||||
|
# in the Software without restriction, including without limitation the rights
|
||||||
|
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
# copies of the Software, and to permit persons to whom the Software is
|
||||||
|
# furnished to do so, subject to the following conditions:
|
||||||
|
#
|
||||||
|
# The above copyright notice and this permission notice shall be included in all
|
||||||
|
# copies or substantial portions of the Software.
|
||||||
|
#
|
||||||
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
# SOFTWARE.
|
||||||
|
|
||||||
|
option(SANITIZE_UNDEFINED
|
||||||
|
"Enable UndefinedBehaviorSanitizer for sanitized targets." Off)
|
||||||
|
|
||||||
|
set(FLAG_CANDIDATES
|
||||||
|
"-g -fsanitize=undefined"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
include(sanitize-helpers)
|
||||||
|
|
||||||
|
if (SANITIZE_UNDEFINED)
|
||||||
|
sanitizer_check_compiler_flags("${FLAG_CANDIDATES}"
|
||||||
|
"UndefinedBehaviorSanitizer" "UBSan")
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
function (add_sanitize_undefined TARGET)
|
||||||
|
if (NOT SANITIZE_UNDEFINED)
|
||||||
|
return()
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
saitizer_add_flags(${TARGET} "UndefinedBehaviorSanitizer" "UBSan")
|
||||||
|
endfunction ()
|
|
@ -0,0 +1,55 @@
|
||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
# The MIT License (MIT)
|
||||||
|
#
|
||||||
|
# Copyright (c)
|
||||||
|
# 2013 Matthew Arsenault
|
||||||
|
# 2015-2016 RWTH Aachen University, Federal Republic of Germany
|
||||||
|
#
|
||||||
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
# of this software and associated documentation files (the "Software"), to deal
|
||||||
|
# in the Software without restriction, including without limitation the rights
|
||||||
|
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
# copies of the Software, and to permit persons to whom the Software is
|
||||||
|
# furnished to do so, subject to the following conditions:
|
||||||
|
#
|
||||||
|
# The above copyright notice and this permission notice shall be included in all
|
||||||
|
# copies or substantial portions of the Software.
|
||||||
|
#
|
||||||
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
# SOFTWARE.
|
||||||
|
|
||||||
|
# This script is a wrapper for AddressSanitizer. In some special cases you need
|
||||||
|
# to preload AddressSanitizer to avoid error messages - e.g. if you're
|
||||||
|
# preloading another library to your application. At the moment this script will
|
||||||
|
# only do something, if we're running on a Linux platform. OSX might not be
|
||||||
|
# affected.
|
||||||
|
|
||||||
|
|
||||||
|
# Exit immediately, if platform is not Linux.
|
||||||
|
if [ "$(uname)" != "Linux" ]
|
||||||
|
then
|
||||||
|
exec $@
|
||||||
|
fi
|
||||||
|
|
||||||
|
|
||||||
|
# Get the used libasan of the application ($1). If a libasan was found, it will
|
||||||
|
# be prepended to LD_PRELOAD.
|
||||||
|
libasan=$(ldd $1 | grep libasan | sed "s/^[[:space:]]//" | cut -d' ' -f1)
|
||||||
|
if [ -n "$libasan" ]
|
||||||
|
then
|
||||||
|
if [ -n "$LD_PRELOAD" ]
|
||||||
|
then
|
||||||
|
export LD_PRELOAD="$libasan:$LD_PRELOAD"
|
||||||
|
else
|
||||||
|
export LD_PRELOAD="$libasan"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Execute the application.
|
||||||
|
exec $@
|
|
@ -0,0 +1,170 @@
|
||||||
|
# The MIT License (MIT)
|
||||||
|
#
|
||||||
|
# Copyright (c)
|
||||||
|
# 2013 Matthew Arsenault
|
||||||
|
# 2015-2016 RWTH Aachen University, Federal Republic of Germany
|
||||||
|
#
|
||||||
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
# of this software and associated documentation files (the "Software"), to deal
|
||||||
|
# in the Software without restriction, including without limitation the rights
|
||||||
|
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
# copies of the Software, and to permit persons to whom the Software is
|
||||||
|
# furnished to do so, subject to the following conditions:
|
||||||
|
#
|
||||||
|
# The above copyright notice and this permission notice shall be included in all
|
||||||
|
# copies or substantial portions of the Software.
|
||||||
|
#
|
||||||
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
# SOFTWARE.
|
||||||
|
|
||||||
|
# Helper function to get the language of a source file.
|
||||||
|
function (sanitizer_lang_of_source FILE RETURN_VAR)
|
||||||
|
get_filename_component(FILE_EXT "${FILE}" EXT)
|
||||||
|
string(TOLOWER "${FILE_EXT}" FILE_EXT)
|
||||||
|
string(SUBSTRING "${FILE_EXT}" 1 -1 FILE_EXT)
|
||||||
|
|
||||||
|
get_property(ENABLED_LANGUAGES GLOBAL PROPERTY ENABLED_LANGUAGES)
|
||||||
|
foreach (LANG ${ENABLED_LANGUAGES})
|
||||||
|
list(FIND CMAKE_${LANG}_SOURCE_FILE_EXTENSIONS "${FILE_EXT}" TEMP)
|
||||||
|
if (NOT ${TEMP} EQUAL -1)
|
||||||
|
set(${RETURN_VAR} "${LANG}" PARENT_SCOPE)
|
||||||
|
return()
|
||||||
|
endif ()
|
||||||
|
endforeach()
|
||||||
|
|
||||||
|
set(${RETURN_VAR} "" PARENT_SCOPE)
|
||||||
|
endfunction ()
|
||||||
|
|
||||||
|
|
||||||
|
# Helper function to get compilers used by a target.
|
||||||
|
function (sanitizer_target_compilers TARGET RETURN_VAR)
|
||||||
|
# Check if all sources for target use the same compiler. If a target uses
|
||||||
|
# e.g. C and Fortran mixed and uses different compilers (e.g. clang and
|
||||||
|
# gfortran) this can trigger huge problems, because different compilers may
|
||||||
|
# use different implementations for sanitizers.
|
||||||
|
set(BUFFER "")
|
||||||
|
get_target_property(TSOURCES ${TARGET} SOURCES)
|
||||||
|
foreach (FILE ${TSOURCES})
|
||||||
|
# If expression was found, FILE is a generator-expression for an object
|
||||||
|
# library. Object libraries will be ignored.
|
||||||
|
string(REGEX MATCH "TARGET_OBJECTS:([^ >]+)" _file ${FILE})
|
||||||
|
if ("${_file}" STREQUAL "")
|
||||||
|
sanitizer_lang_of_source(${FILE} LANG)
|
||||||
|
if (LANG)
|
||||||
|
list(APPEND BUFFER ${CMAKE_${LANG}_COMPILER_ID})
|
||||||
|
endif ()
|
||||||
|
endif ()
|
||||||
|
endforeach ()
|
||||||
|
|
||||||
|
list(REMOVE_DUPLICATES BUFFER)
|
||||||
|
set(${RETURN_VAR} "${BUFFER}" PARENT_SCOPE)
|
||||||
|
endfunction ()
|
||||||
|
|
||||||
|
|
||||||
|
# Helper function to check compiler flags for language compiler.
|
||||||
|
function (sanitizer_check_compiler_flag FLAG LANG VARIABLE)
|
||||||
|
if (${LANG} STREQUAL "C")
|
||||||
|
include(CheckCCompilerFlag)
|
||||||
|
check_c_compiler_flag("${FLAG}" ${VARIABLE})
|
||||||
|
|
||||||
|
elseif (${LANG} STREQUAL "CXX")
|
||||||
|
include(CheckCXXCompilerFlag)
|
||||||
|
check_cxx_compiler_flag("${FLAG}" ${VARIABLE})
|
||||||
|
|
||||||
|
elseif (${LANG} STREQUAL "Fortran")
|
||||||
|
# CheckFortranCompilerFlag was introduced in CMake 3.x. To be compatible
|
||||||
|
# with older Cmake versions, we will check if this module is present
|
||||||
|
# before we use it. Otherwise we will define Fortran coverage support as
|
||||||
|
# not available.
|
||||||
|
include(CheckFortranCompilerFlag OPTIONAL RESULT_VARIABLE INCLUDED)
|
||||||
|
if (INCLUDED)
|
||||||
|
check_fortran_compiler_flag("${FLAG}" ${VARIABLE})
|
||||||
|
elseif (NOT CMAKE_REQUIRED_QUIET)
|
||||||
|
message(STATUS "Performing Test ${VARIABLE}")
|
||||||
|
message(STATUS "Performing Test ${VARIABLE}"
|
||||||
|
" - Failed (Check not supported)")
|
||||||
|
endif ()
|
||||||
|
endif()
|
||||||
|
endfunction ()
|
||||||
|
|
||||||
|
|
||||||
|
# Helper function to test compiler flags.
|
||||||
|
function (sanitizer_check_compiler_flags FLAG_CANDIDATES NAME PREFIX)
|
||||||
|
set(CMAKE_REQUIRED_QUIET ${${PREFIX}_FIND_QUIETLY})
|
||||||
|
|
||||||
|
get_property(ENABLED_LANGUAGES GLOBAL PROPERTY ENABLED_LANGUAGES)
|
||||||
|
foreach (LANG ${ENABLED_LANGUAGES})
|
||||||
|
# Sanitizer flags are not dependend on language, but the used compiler.
|
||||||
|
# So instead of searching flags foreach language, search flags foreach
|
||||||
|
# compiler used.
|
||||||
|
set(COMPILER ${CMAKE_${LANG}_COMPILER_ID})
|
||||||
|
if (NOT DEFINED ${PREFIX}_${COMPILER}_FLAGS)
|
||||||
|
foreach (FLAG ${FLAG_CANDIDATES})
|
||||||
|
if(NOT CMAKE_REQUIRED_QUIET)
|
||||||
|
message(STATUS "Try ${COMPILER} ${NAME} flag = [${FLAG}]")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
set(CMAKE_REQUIRED_FLAGS "${FLAG}")
|
||||||
|
unset(${PREFIX}_FLAG_DETECTED CACHE)
|
||||||
|
sanitizer_check_compiler_flag("${FLAG}" ${LANG}
|
||||||
|
${PREFIX}_FLAG_DETECTED)
|
||||||
|
|
||||||
|
if (${PREFIX}_FLAG_DETECTED)
|
||||||
|
# If compiler is a GNU compiler, search for static flag, if
|
||||||
|
# SANITIZE_LINK_STATIC is enabled.
|
||||||
|
if (SANITIZE_LINK_STATIC AND (${COMPILER} STREQUAL "GNU"))
|
||||||
|
string(TOLOWER ${PREFIX} PREFIX_lower)
|
||||||
|
sanitizer_check_compiler_flag(
|
||||||
|
"-static-lib${PREFIX_lower}" ${LANG}
|
||||||
|
${PREFIX}_STATIC_FLAG_DETECTED)
|
||||||
|
|
||||||
|
if (${PREFIX}_STATIC_FLAG_DETECTED)
|
||||||
|
set(FLAG "-static-lib${PREFIX_lower} ${FLAG}")
|
||||||
|
endif ()
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
set(${PREFIX}_${COMPILER}_FLAGS "${FLAG}" CACHE STRING
|
||||||
|
"${NAME} flags for ${COMPILER} compiler.")
|
||||||
|
mark_as_advanced(${PREFIX}_${COMPILER}_FLAGS)
|
||||||
|
break()
|
||||||
|
endif ()
|
||||||
|
endforeach ()
|
||||||
|
|
||||||
|
if (NOT ${PREFIX}_FLAG_DETECTED)
|
||||||
|
set(${PREFIX}_${COMPILER}_FLAGS "" CACHE STRING
|
||||||
|
"${NAME} flags for ${COMPILER} compiler.")
|
||||||
|
mark_as_advanced(${PREFIX}_${COMPILER}_FLAGS)
|
||||||
|
|
||||||
|
message(WARNING "${NAME} is not available for ${COMPILER} "
|
||||||
|
"compiler. Targets using this compiler will be "
|
||||||
|
"compiled without ${NAME}.")
|
||||||
|
endif ()
|
||||||
|
endif ()
|
||||||
|
endforeach ()
|
||||||
|
endfunction ()
|
||||||
|
|
||||||
|
|
||||||
|
# Helper to assign sanitizer flags for TARGET.
|
||||||
|
function (saitizer_add_flags TARGET NAME PREFIX)
|
||||||
|
# Get list of compilers used by target and check, if sanitizer is available
|
||||||
|
# for this target. Other compiler checks like check for conflicting
|
||||||
|
# compilers will be done in add_sanitizers function.
|
||||||
|
sanitizer_target_compilers(${TARGET} TARGET_COMPILER)
|
||||||
|
list(LENGTH TARGET_COMPILER NUM_COMPILERS)
|
||||||
|
if ("${${PREFIX}_${TARGET_COMPILER}_FLAGS}" STREQUAL "")
|
||||||
|
return()
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# Set compile- and link-flags for target.
|
||||||
|
set_property(TARGET ${TARGET} APPEND_STRING
|
||||||
|
PROPERTY COMPILE_FLAGS " ${${PREFIX}_${TARGET_COMPILER}_FLAGS}")
|
||||||
|
set_property(TARGET ${TARGET} APPEND_STRING
|
||||||
|
PROPERTY COMPILE_FLAGS " ${SanBlist_${TARGET_COMPILER}_FLAGS}")
|
||||||
|
set_property(TARGET ${TARGET} APPEND_STRING
|
||||||
|
PROPERTY LINK_FLAGS " ${${PREFIX}_${TARGET_COMPILER}_FLAGS}")
|
||||||
|
endfunction ()
|
|
@ -0,0 +1,36 @@
|
||||||
|
{
|
||||||
|
snd_config_update-malloc
|
||||||
|
Memcheck:Leak
|
||||||
|
fun:malloc
|
||||||
|
...
|
||||||
|
fun:snd_config_update_r
|
||||||
|
}
|
||||||
|
{
|
||||||
|
snd1_dlobj_cache_get-malloc
|
||||||
|
Memcheck:Leak
|
||||||
|
fun:malloc
|
||||||
|
...
|
||||||
|
fun:snd1_dlobj_cache_get
|
||||||
|
}
|
||||||
|
{
|
||||||
|
parse_defs-malloc
|
||||||
|
Memcheck:Leak
|
||||||
|
fun:malloc
|
||||||
|
...
|
||||||
|
fun:parse_defs
|
||||||
|
}
|
||||||
|
{
|
||||||
|
parse_defs-calloc
|
||||||
|
Memcheck:Leak
|
||||||
|
fun:calloc
|
||||||
|
...
|
||||||
|
fun:parse_defs
|
||||||
|
}
|
||||||
|
{
|
||||||
|
pa_client_conf_from_x11-malloc
|
||||||
|
Memcheck:Leak
|
||||||
|
fun:malloc
|
||||||
|
...
|
||||||
|
fun:pa_client_conf_from_x11
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,655 @@
|
||||||
|
/*
|
||||||
|
* Copyright © 2011 Mozilla Foundation
|
||||||
|
*
|
||||||
|
* This program is made available under an ISC-style license. See the
|
||||||
|
* accompanying file LICENSE for details.
|
||||||
|
*/
|
||||||
|
#if !defined(CUBEB_c2f983e9_c96f_e71c_72c3_bbf62992a382)
|
||||||
|
#define CUBEB_c2f983e9_c96f_e71c_72c3_bbf62992a382
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include "cubeb_export.h"
|
||||||
|
|
||||||
|
#if defined(__cplusplus)
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/** @mainpage
|
||||||
|
|
||||||
|
@section intro Introduction
|
||||||
|
|
||||||
|
This is the documentation for the <tt>libcubeb</tt> C API.
|
||||||
|
<tt>libcubeb</tt> is a callback-based audio API library allowing the
|
||||||
|
authoring of portable multiplatform audio playback and recording.
|
||||||
|
|
||||||
|
@section example Example code
|
||||||
|
|
||||||
|
This example shows how to create a duplex stream that pipes the microphone
|
||||||
|
to the speakers, with minimal latency and the proper sample-rate for the
|
||||||
|
platform.
|
||||||
|
|
||||||
|
@code
|
||||||
|
cubeb * app_ctx;
|
||||||
|
cubeb_init(&app_ctx, "Example Application");
|
||||||
|
int rv;
|
||||||
|
int rate;
|
||||||
|
int latency_frames;
|
||||||
|
uint64_t ts;
|
||||||
|
|
||||||
|
rv = cubeb_get_min_latency(app_ctx, output_params, &latency_frames);
|
||||||
|
if (rv != CUBEB_OK) {
|
||||||
|
fprintf(stderr, "Could not get minimum latency");
|
||||||
|
return rv;
|
||||||
|
}
|
||||||
|
|
||||||
|
rv = cubeb_get_preferred_sample_rate(app_ctx, output_params, &rate);
|
||||||
|
if (rv != CUBEB_OK) {
|
||||||
|
fprintf(stderr, "Could not get preferred sample-rate");
|
||||||
|
return rv;
|
||||||
|
}
|
||||||
|
|
||||||
|
cubeb_stream_params output_params;
|
||||||
|
output_params.format = CUBEB_SAMPLE_FLOAT32NE;
|
||||||
|
output_params.rate = rate;
|
||||||
|
output_params.channels = 2;
|
||||||
|
|
||||||
|
cubeb_stream_params input_params;
|
||||||
|
output_params.format = CUBEB_SAMPLE_FLOAT32NE;
|
||||||
|
output_params.rate = rate;
|
||||||
|
output_params.channels = 1;
|
||||||
|
|
||||||
|
cubeb_stream * stm;
|
||||||
|
rv = cubeb_stream_init(app_ctx, &stm, "Example Stream 1",
|
||||||
|
NULL, input_params,
|
||||||
|
NULL, output_params,
|
||||||
|
latency_frames,
|
||||||
|
data_cb, state_cb,
|
||||||
|
NULL);
|
||||||
|
if (rv != CUBEB_OK) {
|
||||||
|
fprintf(stderr, "Could not open the stream");
|
||||||
|
return rv;
|
||||||
|
}
|
||||||
|
|
||||||
|
rv = cubeb_stream_start(stm);
|
||||||
|
if (rv != CUBEB_OK) {
|
||||||
|
fprintf(stderr, "Could not start the stream");
|
||||||
|
return rv;
|
||||||
|
}
|
||||||
|
for (;;) {
|
||||||
|
cubeb_stream_get_position(stm, &ts);
|
||||||
|
printf("time=%llu\n", ts);
|
||||||
|
sleep(1);
|
||||||
|
}
|
||||||
|
rv = cubeb_stream_stop(stm);
|
||||||
|
if (rv != CUBEB_OK) {
|
||||||
|
fprintf(stderr, "Could not stop the stream");
|
||||||
|
return rv;
|
||||||
|
}
|
||||||
|
|
||||||
|
cubeb_stream_destroy(stm);
|
||||||
|
cubeb_destroy(app_ctx);
|
||||||
|
@endcode
|
||||||
|
|
||||||
|
@code
|
||||||
|
long data_cb(cubeb_stream * stm, void * user,
|
||||||
|
void * input_buffer, void * output_buffer, long nframes)
|
||||||
|
{
|
||||||
|
float * in = input_buffer;
|
||||||
|
float * out = output_buffer;
|
||||||
|
|
||||||
|
for (i = 0; i < nframes; ++i) {
|
||||||
|
for (c = 0; c < 2; ++c) {
|
||||||
|
buf[i][c] = in[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nframes;
|
||||||
|
}
|
||||||
|
@endcode
|
||||||
|
|
||||||
|
@code
|
||||||
|
void state_cb(cubeb_stream * stm, void * user, cubeb_state state)
|
||||||
|
{
|
||||||
|
printf("state=%d\n", state);
|
||||||
|
}
|
||||||
|
@endcode
|
||||||
|
*/
|
||||||
|
|
||||||
|
/** @file
|
||||||
|
The <tt>libcubeb</tt> C API. */
|
||||||
|
|
||||||
|
typedef struct cubeb cubeb; /**< Opaque handle referencing the application state. */
|
||||||
|
typedef struct cubeb_stream cubeb_stream; /**< Opaque handle referencing the stream state. */
|
||||||
|
|
||||||
|
/** Sample format enumeration. */
|
||||||
|
typedef enum {
|
||||||
|
/**< Little endian 16-bit signed PCM. */
|
||||||
|
CUBEB_SAMPLE_S16LE,
|
||||||
|
/**< Big endian 16-bit signed PCM. */
|
||||||
|
CUBEB_SAMPLE_S16BE,
|
||||||
|
/**< Little endian 32-bit IEEE floating point PCM. */
|
||||||
|
CUBEB_SAMPLE_FLOAT32LE,
|
||||||
|
/**< Big endian 32-bit IEEE floating point PCM. */
|
||||||
|
CUBEB_SAMPLE_FLOAT32BE,
|
||||||
|
#if defined(WORDS_BIGENDIAN) || defined(__BIG_ENDIAN__)
|
||||||
|
/**< Native endian 16-bit signed PCM. */
|
||||||
|
CUBEB_SAMPLE_S16NE = CUBEB_SAMPLE_S16BE,
|
||||||
|
/**< Native endian 32-bit IEEE floating point PCM. */
|
||||||
|
CUBEB_SAMPLE_FLOAT32NE = CUBEB_SAMPLE_FLOAT32BE
|
||||||
|
#else
|
||||||
|
/**< Native endian 16-bit signed PCM. */
|
||||||
|
CUBEB_SAMPLE_S16NE = CUBEB_SAMPLE_S16LE,
|
||||||
|
/**< Native endian 32-bit IEEE floating point PCM. */
|
||||||
|
CUBEB_SAMPLE_FLOAT32NE = CUBEB_SAMPLE_FLOAT32LE
|
||||||
|
#endif
|
||||||
|
} cubeb_sample_format;
|
||||||
|
|
||||||
|
#if defined(__ANDROID__)
|
||||||
|
/**
|
||||||
|
* This maps to the underlying stream types on supported platforms, e.g.
|
||||||
|
* Android.
|
||||||
|
*/
|
||||||
|
typedef enum {
|
||||||
|
CUBEB_STREAM_TYPE_VOICE_CALL = 0,
|
||||||
|
CUBEB_STREAM_TYPE_SYSTEM = 1,
|
||||||
|
CUBEB_STREAM_TYPE_RING = 2,
|
||||||
|
CUBEB_STREAM_TYPE_MUSIC = 3,
|
||||||
|
CUBEB_STREAM_TYPE_ALARM = 4,
|
||||||
|
CUBEB_STREAM_TYPE_NOTIFICATION = 5,
|
||||||
|
CUBEB_STREAM_TYPE_BLUETOOTH_SCO = 6,
|
||||||
|
CUBEB_STREAM_TYPE_SYSTEM_ENFORCED = 7,
|
||||||
|
CUBEB_STREAM_TYPE_DTMF = 8,
|
||||||
|
CUBEB_STREAM_TYPE_TTS = 9,
|
||||||
|
CUBEB_STREAM_TYPE_FM = 10,
|
||||||
|
|
||||||
|
CUBEB_STREAM_TYPE_MAX
|
||||||
|
} cubeb_stream_type;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/** An opaque handle used to refer a particular input or output device
|
||||||
|
* across calls. */
|
||||||
|
typedef void const * cubeb_devid;
|
||||||
|
|
||||||
|
/** Level (verbosity) of logging for a particular cubeb context. */
|
||||||
|
typedef enum {
|
||||||
|
CUBEB_LOG_DISABLED = 0, /** < Logging disabled */
|
||||||
|
CUBEB_LOG_NORMAL = 1, /**< Logging lifetime operation (creation/destruction). */
|
||||||
|
CUBEB_LOG_VERBOSE = 2, /**< Verbose logging of callbacks, can have performance implications. */
|
||||||
|
} cubeb_log_level;
|
||||||
|
|
||||||
|
/** SMPTE channel layout (also known as wave order)
|
||||||
|
* DUAL-MONO L R
|
||||||
|
* DUAL-MONO-LFE L R LFE
|
||||||
|
* MONO M
|
||||||
|
* MONO-LFE M LFE
|
||||||
|
* STEREO L R
|
||||||
|
* STEREO-LFE L R LFE
|
||||||
|
* 3F L R C
|
||||||
|
* 3F-LFE L R C LFE
|
||||||
|
* 2F1 L R S
|
||||||
|
* 2F1-LFE L R LFE S
|
||||||
|
* 3F1 L R C S
|
||||||
|
* 3F1-LFE L R C LFE S
|
||||||
|
* 2F2 L R LS RS
|
||||||
|
* 2F2-LFE L R LFE LS RS
|
||||||
|
* 3F2 L R C LS RS
|
||||||
|
* 3F2-LFE L R C LFE LS RS
|
||||||
|
* 3F3R-LFE L R C LFE RC LS RS
|
||||||
|
* 3F4-LFE L R C LFE RLS RRS LS RS
|
||||||
|
*
|
||||||
|
* The abbreviation of channel name is defined in following table:
|
||||||
|
* Abbr Channel name
|
||||||
|
* ---------------------------
|
||||||
|
* M Mono
|
||||||
|
* L Left
|
||||||
|
* R Right
|
||||||
|
* C Center
|
||||||
|
* LS Left Surround
|
||||||
|
* RS Right Surround
|
||||||
|
* RLS Rear Left Surround
|
||||||
|
* RC Rear Center
|
||||||
|
* RRS Rear Right Surround
|
||||||
|
* LFE Low Frequency Effects
|
||||||
|
*/
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
CUBEB_LAYOUT_UNDEFINED, // Indicate the speaker's layout is undefined.
|
||||||
|
CUBEB_LAYOUT_DUAL_MONO,
|
||||||
|
CUBEB_LAYOUT_DUAL_MONO_LFE,
|
||||||
|
CUBEB_LAYOUT_MONO,
|
||||||
|
CUBEB_LAYOUT_MONO_LFE,
|
||||||
|
CUBEB_LAYOUT_STEREO,
|
||||||
|
CUBEB_LAYOUT_STEREO_LFE,
|
||||||
|
CUBEB_LAYOUT_3F,
|
||||||
|
CUBEB_LAYOUT_3F_LFE,
|
||||||
|
CUBEB_LAYOUT_2F1,
|
||||||
|
CUBEB_LAYOUT_2F1_LFE,
|
||||||
|
CUBEB_LAYOUT_3F1,
|
||||||
|
CUBEB_LAYOUT_3F1_LFE,
|
||||||
|
CUBEB_LAYOUT_2F2,
|
||||||
|
CUBEB_LAYOUT_2F2_LFE,
|
||||||
|
CUBEB_LAYOUT_3F2,
|
||||||
|
CUBEB_LAYOUT_3F2_LFE,
|
||||||
|
CUBEB_LAYOUT_3F3R_LFE,
|
||||||
|
CUBEB_LAYOUT_3F4_LFE,
|
||||||
|
CUBEB_LAYOUT_MAX
|
||||||
|
} cubeb_channel_layout;
|
||||||
|
|
||||||
|
/** Stream format initialization parameters. */
|
||||||
|
typedef struct {
|
||||||
|
cubeb_sample_format format; /**< Requested sample format. One of
|
||||||
|
#cubeb_sample_format. */
|
||||||
|
unsigned int rate; /**< Requested sample rate. Valid range is [1000, 192000]. */
|
||||||
|
unsigned int channels; /**< Requested channel count. Valid range is [1, 8]. */
|
||||||
|
cubeb_channel_layout layout; /**< Requested channel layout. This must be consistent with the provided channels. */
|
||||||
|
#if defined(__ANDROID__)
|
||||||
|
cubeb_stream_type stream_type; /**< Used to map Android audio stream types */
|
||||||
|
#endif
|
||||||
|
} cubeb_stream_params;
|
||||||
|
|
||||||
|
/** Audio device description */
|
||||||
|
typedef struct {
|
||||||
|
char * output_name; /**< The name of the output device */
|
||||||
|
char * input_name; /**< The name of the input device */
|
||||||
|
} cubeb_device;
|
||||||
|
|
||||||
|
/** Stream states signaled via state_callback. */
|
||||||
|
typedef enum {
|
||||||
|
CUBEB_STATE_STARTED, /**< Stream started. */
|
||||||
|
CUBEB_STATE_STOPPED, /**< Stream stopped. */
|
||||||
|
CUBEB_STATE_DRAINED, /**< Stream drained. */
|
||||||
|
CUBEB_STATE_ERROR /**< Stream disabled due to error. */
|
||||||
|
} cubeb_state;
|
||||||
|
|
||||||
|
/** Result code enumeration. */
|
||||||
|
enum {
|
||||||
|
CUBEB_OK = 0, /**< Success. */
|
||||||
|
CUBEB_ERROR = -1, /**< Unclassified error. */
|
||||||
|
CUBEB_ERROR_INVALID_FORMAT = -2, /**< Unsupported #cubeb_stream_params requested. */
|
||||||
|
CUBEB_ERROR_INVALID_PARAMETER = -3, /**< Invalid parameter specified. */
|
||||||
|
CUBEB_ERROR_NOT_SUPPORTED = -4, /**< Optional function not implemented in current backend. */
|
||||||
|
CUBEB_ERROR_DEVICE_UNAVAILABLE = -5 /**< Device specified by #cubeb_devid not available. */
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether a particular device is an input device (e.g. a microphone), or an
|
||||||
|
* output device (e.g. headphones). */
|
||||||
|
typedef enum {
|
||||||
|
CUBEB_DEVICE_TYPE_UNKNOWN,
|
||||||
|
CUBEB_DEVICE_TYPE_INPUT,
|
||||||
|
CUBEB_DEVICE_TYPE_OUTPUT
|
||||||
|
} cubeb_device_type;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The state of a device.
|
||||||
|
*/
|
||||||
|
typedef enum {
|
||||||
|
CUBEB_DEVICE_STATE_DISABLED, /**< The device has been disabled at the system 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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Architecture specific sample type.
|
||||||
|
*/
|
||||||
|
typedef enum {
|
||||||
|
CUBEB_DEVICE_FMT_S16LE = 0x0010, /**< 16-bit integers, Little Endian. */
|
||||||
|
CUBEB_DEVICE_FMT_S16BE = 0x0020, /**< 16-bit integers, Big Endian. */
|
||||||
|
CUBEB_DEVICE_FMT_F32LE = 0x1000, /**< 32-bit floating point, Little Endian. */
|
||||||
|
CUBEB_DEVICE_FMT_F32BE = 0x2000 /**< 32-bit floating point, Big Endian. */
|
||||||
|
} cubeb_device_fmt;
|
||||||
|
|
||||||
|
#if defined(WORDS_BIGENDIAN) || defined(__BIG_ENDIAN__)
|
||||||
|
/** 16-bit integers, native endianess, when on a Big Endian environment. */
|
||||||
|
#define CUBEB_DEVICE_FMT_S16NE CUBEB_DEVICE_FMT_S16BE
|
||||||
|
/** 32-bit floating points, native endianess, when on a Big Endian environment. */
|
||||||
|
#define CUBEB_DEVICE_FMT_F32NE CUBEB_DEVICE_FMT_F32BE
|
||||||
|
#else
|
||||||
|
/** 16-bit integers, native endianess, when on a Little Endian environment. */
|
||||||
|
#define CUBEB_DEVICE_FMT_S16NE CUBEB_DEVICE_FMT_S16LE
|
||||||
|
/** 32-bit floating points, native endianess, when on a Little Endian
|
||||||
|
* environment. */
|
||||||
|
#define CUBEB_DEVICE_FMT_F32NE CUBEB_DEVICE_FMT_F32LE
|
||||||
|
#endif
|
||||||
|
/** All the 16-bit integers types. */
|
||||||
|
#define CUBEB_DEVICE_FMT_S16_MASK (CUBEB_DEVICE_FMT_S16LE | CUBEB_DEVICE_FMT_S16BE)
|
||||||
|
/** All the 32-bit floating points types. */
|
||||||
|
#define CUBEB_DEVICE_FMT_F32_MASK (CUBEB_DEVICE_FMT_F32LE | CUBEB_DEVICE_FMT_F32BE)
|
||||||
|
/** All the device formats types. */
|
||||||
|
#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
|
||||||
|
* used, this can control inter-stream interruption, ducking, and volume
|
||||||
|
* control.
|
||||||
|
*/
|
||||||
|
typedef enum {
|
||||||
|
CUBEB_DEVICE_PREF_NONE = 0x00,
|
||||||
|
CUBEB_DEVICE_PREF_MULTIMEDIA = 0x01,
|
||||||
|
CUBEB_DEVICE_PREF_VOICE = 0x02,
|
||||||
|
CUBEB_DEVICE_PREF_NOTIFICATION = 0x04,
|
||||||
|
CUBEB_DEVICE_PREF_ALL = 0x0F
|
||||||
|
} cubeb_device_pref;
|
||||||
|
|
||||||
|
/** This structure holds the characteristics
|
||||||
|
* of an input or output audio device. It is obtained using
|
||||||
|
* `cubeb_enumerate_devices`, which returns these structures via
|
||||||
|
* `cubeb_device_collection` and must be destroyed via
|
||||||
|
* `cubeb_device_collection_destroy`. */
|
||||||
|
typedef struct {
|
||||||
|
cubeb_devid devid; /**< Device identifier handle. */
|
||||||
|
char const * device_id; /**< Device identifier which might be presented in a UI. */
|
||||||
|
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. */
|
||||||
|
|
||||||
|
cubeb_device_type type; /**< Type of device (Input/Output). */
|
||||||
|
cubeb_device_state state; /**< State of device disabled/enabled/unplugged. */
|
||||||
|
cubeb_device_pref preferred;/**< Preferred device. */
|
||||||
|
|
||||||
|
cubeb_device_fmt format; /**< Sample format supported. */
|
||||||
|
cubeb_device_fmt default_format; /**< The default sample format for this device. */
|
||||||
|
unsigned int max_channels; /**< Channels. */
|
||||||
|
unsigned int default_rate; /**< Default/Preferred sample rate. */
|
||||||
|
unsigned int max_rate; /**< Maximum sample rate supported. */
|
||||||
|
unsigned int min_rate; /**< Minimum sample rate supported. */
|
||||||
|
|
||||||
|
unsigned int latency_lo; /**< Lowest possible latency in frames. */
|
||||||
|
unsigned int latency_hi; /**< Higest possible latency in frames. */
|
||||||
|
} cubeb_device_info;
|
||||||
|
|
||||||
|
/** Device collection.
|
||||||
|
* Returned by `cubeb_enumerate_devices` and destroyed by
|
||||||
|
* `cubeb_device_collection_destroy`. */
|
||||||
|
typedef struct {
|
||||||
|
cubeb_device_info * device; /**< Array of pointers to device info. */
|
||||||
|
size_t count; /**< Device count in collection. */
|
||||||
|
} cubeb_device_collection;
|
||||||
|
|
||||||
|
/** User supplied data callback.
|
||||||
|
- Calling other cubeb functions from this callback is unsafe.
|
||||||
|
- The code in the callback should be non-blocking.
|
||||||
|
- Returning less than the number of frames this callback asks for or
|
||||||
|
provides puts the stream in drain mode. This callback will not be called
|
||||||
|
again, and the state callback will be called with CUBEB_STATE_DRAINED when
|
||||||
|
all the frames have been output.
|
||||||
|
@param stream The stream for which this callback fired.
|
||||||
|
@param user_ptr The pointer passed to cubeb_stream_init.
|
||||||
|
@param input_buffer A pointer containing the input data, or nullptr
|
||||||
|
if this is an output-only stream.
|
||||||
|
@param output_buffer A pointer to a buffer to be filled with audio samples,
|
||||||
|
or nullptr if this is an input-only stream.
|
||||||
|
@param nframes The number of frames of the two buffer.
|
||||||
|
@retval Number of frames written to the output buffer. If this number is
|
||||||
|
less than nframes, then the stream will start to drain.
|
||||||
|
@retval CUBEB_ERROR on error, in which case the data callback will stop
|
||||||
|
and the stream will enter a shutdown state. */
|
||||||
|
typedef long (* cubeb_data_callback)(cubeb_stream * stream,
|
||||||
|
void * user_ptr,
|
||||||
|
void const * input_buffer,
|
||||||
|
void * output_buffer,
|
||||||
|
long nframes);
|
||||||
|
|
||||||
|
/** User supplied state callback.
|
||||||
|
@param stream The stream for this this callback fired.
|
||||||
|
@param user_ptr The pointer passed to cubeb_stream_init.
|
||||||
|
@param state The new state of the stream. */
|
||||||
|
typedef void (* cubeb_state_callback)(cubeb_stream * stream,
|
||||||
|
void * user_ptr,
|
||||||
|
cubeb_state state);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* User supplied callback called when the underlying device changed.
|
||||||
|
* @param user The pointer passed to cubeb_stream_init. */
|
||||||
|
typedef void (* cubeb_device_changed_callback)(void * user_ptr);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* User supplied callback called when the underlying device collection changed.
|
||||||
|
* @param context A pointer to the cubeb context.
|
||||||
|
* @param user_ptr The pointer passed to cubeb_stream_init. */
|
||||||
|
typedef void (* cubeb_device_collection_changed_callback)(cubeb * context,
|
||||||
|
void * user_ptr);
|
||||||
|
|
||||||
|
/** User supplied callback called when a message needs logging. */
|
||||||
|
typedef void (* cubeb_log_callback)(char const * fmt, ...);
|
||||||
|
|
||||||
|
/** Initialize an application context. This will perform any library or
|
||||||
|
application scoped initialization.
|
||||||
|
@param context A out param where an opaque pointer to the application
|
||||||
|
context will be returned.
|
||||||
|
@param context_name A name for the context. Depending on the platform this
|
||||||
|
can appear in different locations.
|
||||||
|
@param backend_name The name of the cubeb backend user desires to select.
|
||||||
|
Accepted values self-documented in cubeb.c: init_oneshot
|
||||||
|
If NULL, a default ordering is used for backend choice.
|
||||||
|
A valid choice overrides all other possible backends,
|
||||||
|
so long as the backend was included at compile time.
|
||||||
|
@retval CUBEB_OK in case of success.
|
||||||
|
@retval CUBEB_ERROR in case of error, for example because the host
|
||||||
|
has no audio hardware. */
|
||||||
|
CUBEB_EXPORT int cubeb_init(cubeb ** context, char const * context_name,
|
||||||
|
char const * backend_name);
|
||||||
|
|
||||||
|
/** Get a read-only string identifying this context's current backend.
|
||||||
|
@param context A pointer to the cubeb context.
|
||||||
|
@retval Read-only string identifying current backend. */
|
||||||
|
CUBEB_EXPORT char const * cubeb_get_backend_id(cubeb * context);
|
||||||
|
|
||||||
|
/** Get the maximum possible number of channels.
|
||||||
|
@param context A pointer to the cubeb context.
|
||||||
|
@param max_channels The maximum number of channels.
|
||||||
|
@retval CUBEB_OK
|
||||||
|
@retval CUBEB_ERROR_INVALID_PARAMETER
|
||||||
|
@retval CUBEB_ERROR_NOT_SUPPORTED
|
||||||
|
@retval CUBEB_ERROR */
|
||||||
|
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
|
||||||
|
when creating a stream for the specified sample rate. This is platform,
|
||||||
|
hardware and backend dependent.
|
||||||
|
@param context A pointer to the cubeb context.
|
||||||
|
@param params On some backends, the minimum achievable latency depends on
|
||||||
|
the characteristics of the stream.
|
||||||
|
@param latency_frames The latency value, in frames, to pass to
|
||||||
|
cubeb_stream_init.
|
||||||
|
@retval CUBEB_OK
|
||||||
|
@retval CUBEB_ERROR_INVALID_PARAMETER
|
||||||
|
@retval CUBEB_ERROR_NOT_SUPPORTED */
|
||||||
|
CUBEB_EXPORT int cubeb_get_min_latency(cubeb * context,
|
||||||
|
cubeb_stream_params params,
|
||||||
|
uint32_t * latency_frames);
|
||||||
|
|
||||||
|
/** Get the preferred sample rate for this backend: this is hardware and
|
||||||
|
platform dependent, and can avoid resampling, and/or trigger fastpaths.
|
||||||
|
@param context A pointer to the cubeb context.
|
||||||
|
@param rate The samplerate (in Hz) the current configuration prefers.
|
||||||
|
@retval CUBEB_OK
|
||||||
|
@retval CUBEB_ERROR_INVALID_PARAMETER
|
||||||
|
@retval CUBEB_ERROR_NOT_SUPPORTED */
|
||||||
|
CUBEB_EXPORT int cubeb_get_preferred_sample_rate(cubeb * context, uint32_t * rate);
|
||||||
|
|
||||||
|
/** Get the preferred layout for this backend: this is hardware and
|
||||||
|
platform dependent.
|
||||||
|
@param context A pointer to the cubeb context.
|
||||||
|
@param layout The layout of the current speaker configuration.
|
||||||
|
@retval CUBEB_OK
|
||||||
|
@retval CUBEB_ERROR_INVALID_PARAMETER
|
||||||
|
@retval CUBEB_ERROR_NOT_SUPPORTED */
|
||||||
|
CUBEB_EXPORT int cubeb_get_preferred_channel_layout(cubeb * context, cubeb_channel_layout * layout);
|
||||||
|
|
||||||
|
/** Destroy an application context. This must be called after all stream have
|
||||||
|
* been destroyed.
|
||||||
|
@param context A pointer to the cubeb context.*/
|
||||||
|
CUBEB_EXPORT void cubeb_destroy(cubeb * context);
|
||||||
|
|
||||||
|
/** Initialize a stream associated with the supplied application context.
|
||||||
|
@param context A pointer to the cubeb context.
|
||||||
|
@param stream An out parameter to be filled with the an opaque pointer to a
|
||||||
|
cubeb stream.
|
||||||
|
@param stream_name A name for this stream.
|
||||||
|
@param input_device Device for the input side of the stream. If NULL the
|
||||||
|
default input device is used.
|
||||||
|
@param input_stream_params Parameters for the input side of the stream, or
|
||||||
|
NULL if this stream is output only.
|
||||||
|
@param output_device Device for the output side of the stream. If NULL the
|
||||||
|
default output device is used.
|
||||||
|
@param output_stream_params Parameters for the output side of the stream, or
|
||||||
|
NULL if this stream is input only.
|
||||||
|
@param latency_frames Stream latency in frames. Valid range
|
||||||
|
is [1, 96000].
|
||||||
|
@param data_callback Will be called to preroll data before playback is
|
||||||
|
started by cubeb_stream_start.
|
||||||
|
@param state_callback A pointer to a state callback.
|
||||||
|
@param user_ptr A pointer that will be passed to the callbacks. This pointer
|
||||||
|
must outlive the life time of the stream.
|
||||||
|
@retval CUBEB_OK
|
||||||
|
@retval CUBEB_ERROR
|
||||||
|
@retval CUBEB_ERROR_INVALID_FORMAT
|
||||||
|
@retval CUBEB_ERROR_DEVICE_UNAVAILABLE */
|
||||||
|
CUBEB_EXPORT int cubeb_stream_init(cubeb * context,
|
||||||
|
cubeb_stream ** stream,
|
||||||
|
char const * stream_name,
|
||||||
|
cubeb_devid input_device,
|
||||||
|
cubeb_stream_params * input_stream_params,
|
||||||
|
cubeb_devid output_device,
|
||||||
|
cubeb_stream_params * output_stream_params,
|
||||||
|
unsigned int latency_frames,
|
||||||
|
cubeb_data_callback data_callback,
|
||||||
|
cubeb_state_callback state_callback,
|
||||||
|
void * user_ptr);
|
||||||
|
|
||||||
|
/** Destroy a stream. `cubeb_stream_stop` MUST be called before destroying a
|
||||||
|
stream.
|
||||||
|
@param stream The stream to destroy. */
|
||||||
|
CUBEB_EXPORT void cubeb_stream_destroy(cubeb_stream * stream);
|
||||||
|
|
||||||
|
/** Start playback.
|
||||||
|
@param stream
|
||||||
|
@retval CUBEB_OK
|
||||||
|
@retval CUBEB_ERROR */
|
||||||
|
CUBEB_EXPORT int cubeb_stream_start(cubeb_stream * stream);
|
||||||
|
|
||||||
|
/** Stop playback.
|
||||||
|
@param stream
|
||||||
|
@retval CUBEB_OK
|
||||||
|
@retval CUBEB_ERROR */
|
||||||
|
CUBEB_EXPORT int cubeb_stream_stop(cubeb_stream * stream);
|
||||||
|
|
||||||
|
/** Get the current stream playback position.
|
||||||
|
@param stream
|
||||||
|
@param position Playback position in frames.
|
||||||
|
@retval CUBEB_OK
|
||||||
|
@retval CUBEB_ERROR */
|
||||||
|
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
|
||||||
|
between the time cubeb acquires the data in the callback and the listener
|
||||||
|
can hear the sound.
|
||||||
|
@param stream
|
||||||
|
@param latency Current approximate stream latency in frames.
|
||||||
|
@retval CUBEB_OK
|
||||||
|
@retval CUBEB_ERROR_NOT_SUPPORTED
|
||||||
|
@retval CUBEB_ERROR */
|
||||||
|
CUBEB_EXPORT int cubeb_stream_get_latency(cubeb_stream * stream, uint32_t * latency);
|
||||||
|
|
||||||
|
/** Set the volume for a stream.
|
||||||
|
@param stream the stream for which to adjust the volume.
|
||||||
|
@param volume a float between 0.0 (muted) and 1.0 (maximum volume)
|
||||||
|
@retval CUBEB_OK
|
||||||
|
@retval CUBEB_ERROR_INVALID_PARAMETER volume is outside [0.0, 1.0] or
|
||||||
|
stream is an invalid pointer
|
||||||
|
@retval CUBEB_ERROR_NOT_SUPPORTED */
|
||||||
|
CUBEB_EXPORT int cubeb_stream_set_volume(cubeb_stream * stream, float volume);
|
||||||
|
|
||||||
|
/** If the stream is stereo, set the left/right panning. If the stream is mono,
|
||||||
|
this has no effect.
|
||||||
|
@param stream the stream for which to change the panning
|
||||||
|
@param panning a number from -1.0 to 1.0. -1.0 means that the stream is
|
||||||
|
fully mixed in the left channel, 1.0 means the stream is fully
|
||||||
|
mixed in the right channel. 0.0 is equal power in the right and
|
||||||
|
left channel (default).
|
||||||
|
@retval CUBEB_OK
|
||||||
|
@retval CUBEB_ERROR_INVALID_PARAMETER if stream is null or if panning is
|
||||||
|
outside the [-1.0, 1.0] range.
|
||||||
|
@retval CUBEB_ERROR_NOT_SUPPORTED
|
||||||
|
@retval CUBEB_ERROR stream is not mono nor stereo */
|
||||||
|
CUBEB_EXPORT int cubeb_stream_set_panning(cubeb_stream * stream, float panning);
|
||||||
|
|
||||||
|
/** Get the current output device for this stream.
|
||||||
|
@param stm the stream for which to query the current output device
|
||||||
|
@param device a pointer in which the current output device will be stored.
|
||||||
|
@retval CUBEB_OK in case of success
|
||||||
|
@retval CUBEB_ERROR_INVALID_PARAMETER if either stm, device or count are
|
||||||
|
invalid pointers
|
||||||
|
@retval CUBEB_ERROR_NOT_SUPPORTED */
|
||||||
|
CUBEB_EXPORT int cubeb_stream_get_current_device(cubeb_stream * stm,
|
||||||
|
cubeb_device ** const device);
|
||||||
|
|
||||||
|
/** Destroy a cubeb_device structure.
|
||||||
|
@param stream the stream passed in cubeb_stream_get_current_device
|
||||||
|
@param devices the devices to destroy
|
||||||
|
@retval CUBEB_OK in case of success
|
||||||
|
@retval CUBEB_ERROR_INVALID_PARAMETER if devices is an invalid pointer
|
||||||
|
@retval CUBEB_ERROR_NOT_SUPPORTED */
|
||||||
|
CUBEB_EXPORT int cubeb_stream_device_destroy(cubeb_stream * stream,
|
||||||
|
cubeb_device * devices);
|
||||||
|
|
||||||
|
/** Set a callback to be notified when the output device changes.
|
||||||
|
@param stream the stream for which to set the callback.
|
||||||
|
@param device_changed_callback a function called whenever the device has
|
||||||
|
changed. Passing NULL allow to unregister a function
|
||||||
|
@retval CUBEB_OK
|
||||||
|
@retval CUBEB_ERROR_INVALID_PARAMETER if either stream or
|
||||||
|
device_changed_callback are invalid pointers.
|
||||||
|
@retval CUBEB_ERROR_NOT_SUPPORTED */
|
||||||
|
CUBEB_EXPORT int cubeb_stream_register_device_changed_callback(cubeb_stream * stream,
|
||||||
|
cubeb_device_changed_callback device_changed_callback);
|
||||||
|
|
||||||
|
/** Returns enumerated devices.
|
||||||
|
@param context
|
||||||
|
@param devtype device type to include
|
||||||
|
@param collection output collection. Must be destroyed with cubeb_device_collection_destroy
|
||||||
|
@retval CUBEB_OK in case of success
|
||||||
|
@retval CUBEB_ERROR_INVALID_PARAMETER if collection is an invalid pointer
|
||||||
|
@retval CUBEB_ERROR_NOT_SUPPORTED */
|
||||||
|
CUBEB_EXPORT int cubeb_enumerate_devices(cubeb * context,
|
||||||
|
cubeb_device_type devtype,
|
||||||
|
cubeb_device_collection * collection);
|
||||||
|
|
||||||
|
/** Destroy a cubeb_device_collection, and its `cubeb_device_info`.
|
||||||
|
@param context
|
||||||
|
@param collection collection to destroy
|
||||||
|
@retval CUBEB_OK
|
||||||
|
@retval CUBEB_ERROR_INVALID_PARAMETER if collection is an invalid pointer */
|
||||||
|
CUBEB_EXPORT int cubeb_device_collection_destroy(cubeb * context,
|
||||||
|
cubeb_device_collection * collection);
|
||||||
|
|
||||||
|
/** Registers a callback which is called when the system detects
|
||||||
|
a new device or a device is removed.
|
||||||
|
@param context
|
||||||
|
@param devtype device type to include
|
||||||
|
@param callback a function called whenever the system device list changes.
|
||||||
|
Passing NULL allow to unregister a function
|
||||||
|
@param user_ptr pointer to user specified data which will be present in
|
||||||
|
subsequent callbacks.
|
||||||
|
@retval CUBEB_ERROR_NOT_SUPPORTED */
|
||||||
|
CUBEB_EXPORT int cubeb_register_device_collection_changed(cubeb * context,
|
||||||
|
cubeb_device_type devtype,
|
||||||
|
cubeb_device_collection_changed_callback callback,
|
||||||
|
void * user_ptr);
|
||||||
|
|
||||||
|
/** Set a callback to be called with a message.
|
||||||
|
@param log_level CUBEB_LOG_VERBOSE, CUBEB_LOG_NORMAL.
|
||||||
|
@param log_callback A function called with a message when there is
|
||||||
|
something to log. Pass NULL to unregister.
|
||||||
|
@retval CUBEB_OK in case of success.
|
||||||
|
@retval CUBEB_ERROR_INVALID_PARAMETER if either context or log_callback are
|
||||||
|
invalid pointers, or if level is not
|
||||||
|
in cubeb_log_level. */
|
||||||
|
CUBEB_EXPORT int cubeb_set_log_callback(cubeb_log_level log_level,
|
||||||
|
cubeb_log_callback log_callback);
|
||||||
|
|
||||||
|
#if defined(__cplusplus)
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif /* CUBEB_c2f983e9_c96f_e71c_72c3_bbf62992a382 */
|
|
@ -0,0 +1,81 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2008 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The following definitions are copied from the android sources. Only the
|
||||||
|
* relevant enum member and values needed are copied.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* From https://android.googlesource.com/platform/frameworks/base/+/android-2.2.3_r2.1/include/utils/Errors.h
|
||||||
|
*/
|
||||||
|
typedef int32_t status_t;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* From https://android.googlesource.com/platform/frameworks/base/+/android-2.2.3_r2.1/include/media/AudioTrack.h
|
||||||
|
*/
|
||||||
|
struct Buffer {
|
||||||
|
uint32_t flags;
|
||||||
|
int channelCount;
|
||||||
|
int format;
|
||||||
|
size_t frameCount;
|
||||||
|
size_t size;
|
||||||
|
union {
|
||||||
|
void* raw;
|
||||||
|
short* i16;
|
||||||
|
int8_t* i8;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
enum event_type {
|
||||||
|
EVENT_MORE_DATA = 0,
|
||||||
|
EVENT_UNDERRUN = 1,
|
||||||
|
EVENT_LOOP_END = 2,
|
||||||
|
EVENT_MARKER = 3,
|
||||||
|
EVENT_NEW_POS = 4,
|
||||||
|
EVENT_BUFFER_END = 5
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* From https://android.googlesource.com/platform/frameworks/base/+/android-2.2.3_r2.1/include/media/AudioSystem.h
|
||||||
|
* and
|
||||||
|
* https://android.googlesource.com/platform/system/core/+/android-4.2.2_r1/include/system/audio.h
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define AUDIO_STREAM_TYPE_MUSIC 3
|
||||||
|
|
||||||
|
enum {
|
||||||
|
AUDIO_CHANNEL_OUT_FRONT_LEFT_ICS = 0x1,
|
||||||
|
AUDIO_CHANNEL_OUT_FRONT_RIGHT_ICS = 0x2,
|
||||||
|
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)
|
||||||
|
} AudioTrack_ChannelMapping_ICS;
|
||||||
|
|
||||||
|
enum {
|
||||||
|
AUDIO_CHANNEL_OUT_FRONT_LEFT_Legacy = 0x4,
|
||||||
|
AUDIO_CHANNEL_OUT_FRONT_RIGHT_Legacy = 0x8,
|
||||||
|
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)
|
||||||
|
} AudioTrack_ChannelMapping_Legacy;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
AUDIO_FORMAT_PCM = 0x00000000,
|
||||||
|
AUDIO_FORMAT_PCM_SUB_16_BIT = 0x1,
|
||||||
|
AUDIO_FORMAT_PCM_16_BIT = (AUDIO_FORMAT_PCM | AUDIO_FORMAT_PCM_SUB_16_BIT),
|
||||||
|
} AudioTrack_SampleType;
|
||||||
|
|
|
@ -0,0 +1,77 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2010 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This file is similar to the file "OpenSLES_AndroidConfiguration.h" found in
|
||||||
|
* the Android NDK, but removes the #ifdef __cplusplus defines, so we can keep
|
||||||
|
* using a C compiler in cubeb.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef OPENSL_ES_ANDROIDCONFIGURATION_H_
|
||||||
|
#define OPENSL_ES_ANDROIDCONFIGURATION_H_
|
||||||
|
|
||||||
|
/*---------------------------------------------------------------------------*/
|
||||||
|
/* Android AudioRecorder configuration */
|
||||||
|
/*---------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
/** Audio recording preset */
|
||||||
|
/** Audio recording preset key */
|
||||||
|
#define SL_ANDROID_KEY_RECORDING_PRESET ((const SLchar*) "androidRecordingPreset")
|
||||||
|
/** Audio recording preset values */
|
||||||
|
/** preset "none" cannot be set, it is used to indicate the current settings
|
||||||
|
* do not match any of the presets. */
|
||||||
|
#define SL_ANDROID_RECORDING_PRESET_NONE ((SLuint32) 0x00000000)
|
||||||
|
/** generic recording configuration on the platform */
|
||||||
|
#define SL_ANDROID_RECORDING_PRESET_GENERIC ((SLuint32) 0x00000001)
|
||||||
|
/** uses the microphone audio source with the same orientation as the camera
|
||||||
|
* if available, the main device microphone otherwise */
|
||||||
|
#define SL_ANDROID_RECORDING_PRESET_CAMCORDER ((SLuint32) 0x00000002)
|
||||||
|
/** uses the main microphone tuned for voice recognition */
|
||||||
|
#define SL_ANDROID_RECORDING_PRESET_VOICE_RECOGNITION ((SLuint32) 0x00000003)
|
||||||
|
/** uses the main microphone tuned for audio communications */
|
||||||
|
#define SL_ANDROID_RECORDING_PRESET_VOICE_COMMUNICATION ((SLuint32) 0x00000004)
|
||||||
|
|
||||||
|
/** Audio recording get session ID (read only) */
|
||||||
|
/** Audio recording get session ID key */
|
||||||
|
#define SL_ANDROID_KEY_RECORDING_SESSION_ID ((const SLchar*) "androidRecordingSessionId")
|
||||||
|
|
||||||
|
/*---------------------------------------------------------------------------*/
|
||||||
|
/* Android AudioPlayer configuration */
|
||||||
|
/*---------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
/** Audio playback stream type */
|
||||||
|
/** Audio playback stream type key */
|
||||||
|
#define SL_ANDROID_KEY_STREAM_TYPE ((const SLchar*) "androidPlaybackStreamType")
|
||||||
|
|
||||||
|
/** Audio playback stream type values */
|
||||||
|
/* same as android.media.AudioManager.STREAM_VOICE_CALL */
|
||||||
|
#define SL_ANDROID_STREAM_VOICE ((SLint32) 0x00000000)
|
||||||
|
/* same as android.media.AudioManager.STREAM_SYSTEM */
|
||||||
|
#define SL_ANDROID_STREAM_SYSTEM ((SLint32) 0x00000001)
|
||||||
|
/* same as android.media.AudioManager.STREAM_RING */
|
||||||
|
#define SL_ANDROID_STREAM_RING ((SLint32) 0x00000002)
|
||||||
|
/* same as android.media.AudioManager.STREAM_MUSIC */
|
||||||
|
#define SL_ANDROID_STREAM_MEDIA ((SLint32) 0x00000003)
|
||||||
|
/* same as android.media.AudioManager.STREAM_ALARM */
|
||||||
|
#define SL_ANDROID_STREAM_ALARM ((SLint32) 0x00000004)
|
||||||
|
/* same as android.media.AudioManager.STREAM_NOTIFICATION */
|
||||||
|
#define SL_ANDROID_STREAM_NOTIFICATION ((SLint32) 0x00000005)
|
||||||
|
/* same as android.media.AudioManager.STREAM_BLUETOOTH_SCO */
|
||||||
|
#define SL_ANDROID_STREAM_BLUETOOTH_SCO ((SLint32) 0x00000006)
|
||||||
|
/* same as android.media.AudioManager.STREAM_SYSTEM_ENFORCED */
|
||||||
|
#define SL_ANDROID_STREAM_SYSTEM_ENFORCED ((SLint32) 0x00000007)
|
||||||
|
|
||||||
|
#endif /* OPENSL_ES_ANDROIDCONFIGURATION_H_ */
|
|
@ -0,0 +1,88 @@
|
||||||
|
/*
|
||||||
|
* Copyright © 2013 Mozilla Foundation
|
||||||
|
*
|
||||||
|
* This program is made available under an ISC-style license. See the
|
||||||
|
* accompanying file LICENSE for details.
|
||||||
|
*/
|
||||||
|
#if !defined(CUBEB_INTERNAL_0eb56756_4e20_4404_a76d_42bf88cd15a5)
|
||||||
|
#define CUBEB_INTERNAL_0eb56756_4e20_4404_a76d_42bf88cd15a5
|
||||||
|
|
||||||
|
#include "cubeb/cubeb.h"
|
||||||
|
#include "cubeb_log.h"
|
||||||
|
#include "cubeb_assert.h"
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#ifdef __clang__
|
||||||
|
#ifndef CLANG_ANALYZER_NORETURN
|
||||||
|
#if __has_feature(attribute_analyzer_noreturn)
|
||||||
|
#define CLANG_ANALYZER_NORETURN __attribute__((analyzer_noreturn))
|
||||||
|
#else
|
||||||
|
#define CLANG_ANALYZER_NORETURN
|
||||||
|
#endif // ifndef CLANG_ANALYZER_NORETURN
|
||||||
|
#endif // __has_feature(attribute_analyzer_noreturn)
|
||||||
|
#else // __clang__
|
||||||
|
#define CLANG_ANALYZER_NORETURN
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined(__cplusplus)
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined(__cplusplus)
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
char const * name;
|
||||||
|
unsigned int const channels;
|
||||||
|
cubeb_channel_layout const layout;
|
||||||
|
} cubeb_layout_map;
|
||||||
|
|
||||||
|
extern cubeb_layout_map const CUBEB_CHANNEL_LAYOUT_MAPS[CUBEB_LAYOUT_MAX];
|
||||||
|
|
||||||
|
struct cubeb_ops {
|
||||||
|
int (* init)(cubeb ** context, char const * context_name);
|
||||||
|
char const * (* get_backend_id)(cubeb * context);
|
||||||
|
int (* get_max_channel_count)(cubeb * context, uint32_t * max_channels);
|
||||||
|
int (* get_min_latency)(cubeb * context,
|
||||||
|
cubeb_stream_params params,
|
||||||
|
uint32_t * latency_ms);
|
||||||
|
int (* get_preferred_sample_rate)(cubeb * context, uint32_t * rate);
|
||||||
|
int (* get_preferred_channel_layout)(cubeb * context, cubeb_channel_layout * layout);
|
||||||
|
int (* enumerate_devices)(cubeb * context, cubeb_device_type type,
|
||||||
|
cubeb_device_collection * collection);
|
||||||
|
int (* device_collection_destroy)(cubeb * context,
|
||||||
|
cubeb_device_collection * collection);
|
||||||
|
void (* destroy)(cubeb * context);
|
||||||
|
int (* stream_init)(cubeb * context,
|
||||||
|
cubeb_stream ** stream,
|
||||||
|
char const * stream_name,
|
||||||
|
cubeb_devid input_device,
|
||||||
|
cubeb_stream_params * input_stream_params,
|
||||||
|
cubeb_devid output_device,
|
||||||
|
cubeb_stream_params * output_stream_params,
|
||||||
|
unsigned int latency,
|
||||||
|
cubeb_data_callback data_callback,
|
||||||
|
cubeb_state_callback state_callback,
|
||||||
|
void * user_ptr);
|
||||||
|
void (* stream_destroy)(cubeb_stream * stream);
|
||||||
|
int (* stream_start)(cubeb_stream * stream);
|
||||||
|
int (* stream_stop)(cubeb_stream * stream);
|
||||||
|
int (* stream_get_position)(cubeb_stream * stream, uint64_t * position);
|
||||||
|
int (* stream_get_latency)(cubeb_stream * stream, uint32_t * latency);
|
||||||
|
int (* stream_set_volume)(cubeb_stream * stream, float volumes);
|
||||||
|
int (* stream_set_panning)(cubeb_stream * stream, float panning);
|
||||||
|
int (* stream_get_current_device)(cubeb_stream * stream,
|
||||||
|
cubeb_device ** const device);
|
||||||
|
int (* stream_device_destroy)(cubeb_stream * stream,
|
||||||
|
cubeb_device * device);
|
||||||
|
int (* stream_register_device_changed_callback)(cubeb_stream * stream,
|
||||||
|
cubeb_device_changed_callback device_changed_callback);
|
||||||
|
int (* register_device_collection_changed)(cubeb * context,
|
||||||
|
cubeb_device_type devtype,
|
||||||
|
cubeb_device_collection_changed_callback callback,
|
||||||
|
void * user_ptr);
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif /* CUBEB_INTERNAL_0eb56756_4e20_4404_a76d_42bf88cd15a5 */
|
|
@ -0,0 +1,43 @@
|
||||||
|
/*
|
||||||
|
* Copyright © 2016 Mozilla Foundation
|
||||||
|
*
|
||||||
|
* This program is made available under an ISC-style license. See the
|
||||||
|
* accompanying file LICENSE for details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _CUBEB_SLES_H_
|
||||||
|
#define _CUBEB_SLES_H_
|
||||||
|
#include <SLES/OpenSLES.h>
|
||||||
|
|
||||||
|
static SLresult
|
||||||
|
cubeb_get_sles_engine(SLObjectItf * pEngine,
|
||||||
|
SLuint32 numOptions,
|
||||||
|
const SLEngineOption * pEngineOptions,
|
||||||
|
SLuint32 numInterfaces,
|
||||||
|
const SLInterfaceID * pInterfaceIds,
|
||||||
|
const SLboolean * pInterfaceRequired)
|
||||||
|
{
|
||||||
|
return slCreateEngine(pEngine,
|
||||||
|
numOptions,
|
||||||
|
pEngineOptions,
|
||||||
|
numInterfaces,
|
||||||
|
pInterfaceIds,
|
||||||
|
pInterfaceRequired);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
cubeb_destroy_sles_engine(SLObjectItf * self)
|
||||||
|
{
|
||||||
|
if (*self != NULL) {
|
||||||
|
(**self)->Destroy(*self);
|
||||||
|
*self = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static SLresult
|
||||||
|
cubeb_realize_sles_engine(SLObjectItf self)
|
||||||
|
{
|
||||||
|
return (*self)->Realize(self, SL_BOOLEAN_FALSE);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
|
@ -0,0 +1 @@
|
||||||
|
#include <speex/speex_resampler.h>
|
|
@ -0,0 +1,630 @@
|
||||||
|
/*
|
||||||
|
* Copyright © 2013 Mozilla Foundation
|
||||||
|
*
|
||||||
|
* This program is made available under an ISC-style license. See the
|
||||||
|
* accompanying file LICENSE for details.
|
||||||
|
*/
|
||||||
|
#undef NDEBUG
|
||||||
|
#include <assert.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include "cubeb/cubeb.h"
|
||||||
|
#include "cubeb-internal.h"
|
||||||
|
|
||||||
|
#define NELEMS(x) ((int) (sizeof(x) / sizeof(x[0])))
|
||||||
|
|
||||||
|
struct cubeb {
|
||||||
|
struct cubeb_ops * ops;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct cubeb_stream {
|
||||||
|
struct cubeb * context;
|
||||||
|
};
|
||||||
|
|
||||||
|
#if defined(USE_PULSE)
|
||||||
|
int pulse_init(cubeb ** context, char const * context_name);
|
||||||
|
#endif
|
||||||
|
#if defined(USE_PULSE_RUST)
|
||||||
|
int pulse_rust_init(cubeb ** contet, char const * context_name);
|
||||||
|
#endif
|
||||||
|
#if defined(USE_JACK)
|
||||||
|
int jack_init (cubeb ** context, char const * context_name);
|
||||||
|
#endif
|
||||||
|
#if defined(USE_ALSA)
|
||||||
|
int alsa_init(cubeb ** context, char const * context_name);
|
||||||
|
#endif
|
||||||
|
#if defined(USE_AUDIOUNIT)
|
||||||
|
int audiounit_init(cubeb ** context, char const * context_name);
|
||||||
|
#endif
|
||||||
|
#if defined(USE_WINMM)
|
||||||
|
int winmm_init(cubeb ** context, char const * context_name);
|
||||||
|
#endif
|
||||||
|
#if defined(USE_WASAPI)
|
||||||
|
int wasapi_init(cubeb ** context, char const * context_name);
|
||||||
|
#endif
|
||||||
|
#if defined(USE_SNDIO)
|
||||||
|
int sndio_init(cubeb ** context, char const * context_name);
|
||||||
|
#endif
|
||||||
|
#if defined(USE_OPENSL)
|
||||||
|
int opensl_init(cubeb ** context, char const * context_name);
|
||||||
|
#endif
|
||||||
|
#if defined(USE_AUDIOTRACK)
|
||||||
|
int audiotrack_init(cubeb ** context, char const * context_name);
|
||||||
|
#endif
|
||||||
|
#if defined(USE_KAI)
|
||||||
|
int kai_init(cubeb ** context, char const * context_name);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static int
|
||||||
|
validate_stream_params(cubeb_stream_params * input_stream_params,
|
||||||
|
cubeb_stream_params * output_stream_params)
|
||||||
|
{
|
||||||
|
XASSERT(input_stream_params || output_stream_params);
|
||||||
|
if (output_stream_params) {
|
||||||
|
if (output_stream_params->rate < 1000 || output_stream_params->rate > 192000 ||
|
||||||
|
output_stream_params->channels < 1 || output_stream_params->channels > 8) {
|
||||||
|
return CUBEB_ERROR_INVALID_FORMAT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (input_stream_params) {
|
||||||
|
if (input_stream_params->rate < 1000 || input_stream_params->rate > 192000 ||
|
||||||
|
input_stream_params->channels < 1 || input_stream_params->channels > 8) {
|
||||||
|
return CUBEB_ERROR_INVALID_FORMAT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Rate and sample format must be the same for input and output, if using a
|
||||||
|
// duplex stream
|
||||||
|
if (input_stream_params && output_stream_params) {
|
||||||
|
if (input_stream_params->rate != output_stream_params->rate ||
|
||||||
|
input_stream_params->format != output_stream_params->format) {
|
||||||
|
return CUBEB_ERROR_INVALID_FORMAT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cubeb_stream_params * params = input_stream_params ?
|
||||||
|
input_stream_params : output_stream_params;
|
||||||
|
|
||||||
|
switch (params->format) {
|
||||||
|
case CUBEB_SAMPLE_S16LE:
|
||||||
|
case CUBEB_SAMPLE_S16BE:
|
||||||
|
case CUBEB_SAMPLE_FLOAT32LE:
|
||||||
|
case CUBEB_SAMPLE_FLOAT32BE:
|
||||||
|
return CUBEB_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
return CUBEB_ERROR_INVALID_FORMAT;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
validate_latency(int latency)
|
||||||
|
{
|
||||||
|
if (latency < 1 || latency > 96000) {
|
||||||
|
return CUBEB_ERROR_INVALID_PARAMETER;
|
||||||
|
}
|
||||||
|
return CUBEB_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
cubeb_init(cubeb ** context, char const * context_name, char const * backend_name)
|
||||||
|
{
|
||||||
|
int (* init_oneshot)(cubeb **, char const *) = NULL;
|
||||||
|
|
||||||
|
if (backend_name != NULL) {
|
||||||
|
if (!strcmp(backend_name, "pulse")) {
|
||||||
|
#if defined(USE_PULSE)
|
||||||
|
init_oneshot = pulse_init;
|
||||||
|
#endif
|
||||||
|
} else if (!strcmp(backend_name, "pulse-rust")) {
|
||||||
|
#if defined(USE_PULSE_RUST)
|
||||||
|
init_oneshot = pulse_rust_init;
|
||||||
|
#endif
|
||||||
|
} else if (!strcmp(backend_name, "jack")) {
|
||||||
|
#if defined(USE_JACK)
|
||||||
|
init_oneshot = jack_init;
|
||||||
|
#endif
|
||||||
|
} else if (!strcmp(backend_name, "alsa")) {
|
||||||
|
#if defined(USE_ALSA)
|
||||||
|
init_oneshot = alsa_init;
|
||||||
|
#endif
|
||||||
|
} else if (!strcmp(backend_name, "audiounit")) {
|
||||||
|
#if defined(USE_AUDIOUNIT)
|
||||||
|
init_oneshot = audiounit_init;
|
||||||
|
#endif
|
||||||
|
} else if (!strcmp(backend_name, "wasapi")) {
|
||||||
|
#if defined(USE_WASAPI)
|
||||||
|
init_oneshot = wasapi_init;
|
||||||
|
#endif
|
||||||
|
} else if (!strcmp(backend_name, "winmm")) {
|
||||||
|
#if defined(USE_WINMM)
|
||||||
|
init_oneshot = winmm_init;
|
||||||
|
#endif
|
||||||
|
} else if (!strcmp(backend_name, "sndio")) {
|
||||||
|
#if defined(USE_SNDIO)
|
||||||
|
init_oneshot = sndio_init;
|
||||||
|
#endif
|
||||||
|
} else if (!strcmp(backend_name, "opensl")) {
|
||||||
|
#if defined(USE_OPENSL)
|
||||||
|
init_oneshot = opensl_init;
|
||||||
|
#endif
|
||||||
|
} else if (!strcmp(backend_name, "audiotrack")) {
|
||||||
|
#if defined(USE_AUDIOTRACK)
|
||||||
|
init_oneshot = audiotrack_init;
|
||||||
|
#endif
|
||||||
|
} else if (!strcmp(backend_name, "kai")) {
|
||||||
|
#if defined(USE_KAI)
|
||||||
|
init_oneshot = kai_init;
|
||||||
|
#endif
|
||||||
|
} else {
|
||||||
|
/* Already set */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int (* default_init[])(cubeb **, char const *) = {
|
||||||
|
/*
|
||||||
|
* init_oneshot must be at the top to allow user
|
||||||
|
* to override all other choices
|
||||||
|
*/
|
||||||
|
init_oneshot,
|
||||||
|
#if defined(USE_PULSE)
|
||||||
|
pulse_init,
|
||||||
|
#endif
|
||||||
|
#if defined(USE_JACK)
|
||||||
|
jack_init,
|
||||||
|
#endif
|
||||||
|
#if defined(USE_ALSA)
|
||||||
|
alsa_init,
|
||||||
|
#endif
|
||||||
|
#if defined(USE_AUDIOUNIT)
|
||||||
|
audiounit_init,
|
||||||
|
#endif
|
||||||
|
#if defined(USE_WASAPI)
|
||||||
|
wasapi_init,
|
||||||
|
#endif
|
||||||
|
#if defined(USE_WINMM)
|
||||||
|
winmm_init,
|
||||||
|
#endif
|
||||||
|
#if defined(USE_SNDIO)
|
||||||
|
sndio_init,
|
||||||
|
#endif
|
||||||
|
#if defined(USE_OPENSL)
|
||||||
|
opensl_init,
|
||||||
|
#endif
|
||||||
|
#if defined(USE_AUDIOTRACK)
|
||||||
|
audiotrack_init,
|
||||||
|
#endif
|
||||||
|
#if defined(USE_KAI)
|
||||||
|
kai_init,
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
int i;
|
||||||
|
|
||||||
|
if (!context) {
|
||||||
|
return CUBEB_ERROR_INVALID_PARAMETER;
|
||||||
|
}
|
||||||
|
|
||||||
|
#define OK(fn) assert((* context)->ops->fn)
|
||||||
|
for (i = 0; i < NELEMS(default_init); ++i) {
|
||||||
|
if (default_init[i] && default_init[i](context, context_name) == CUBEB_OK) {
|
||||||
|
/* Assert that the minimal API is implemented. */
|
||||||
|
OK(get_backend_id);
|
||||||
|
OK(destroy);
|
||||||
|
OK(stream_init);
|
||||||
|
OK(stream_destroy);
|
||||||
|
OK(stream_start);
|
||||||
|
OK(stream_stop);
|
||||||
|
OK(stream_get_position);
|
||||||
|
return CUBEB_OK;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return CUBEB_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
char const *
|
||||||
|
cubeb_get_backend_id(cubeb * context)
|
||||||
|
{
|
||||||
|
if (!context) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return context->ops->get_backend_id(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
cubeb_get_max_channel_count(cubeb * context, uint32_t * max_channels)
|
||||||
|
{
|
||||||
|
if (!context || !max_channels) {
|
||||||
|
return CUBEB_ERROR_INVALID_PARAMETER;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!context->ops->get_max_channel_count) {
|
||||||
|
return CUBEB_ERROR_NOT_SUPPORTED;
|
||||||
|
}
|
||||||
|
|
||||||
|
return context->ops->get_max_channel_count(context, max_channels);
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
cubeb_get_min_latency(cubeb * context, cubeb_stream_params params, uint32_t * latency_ms)
|
||||||
|
{
|
||||||
|
if (!context || !latency_ms) {
|
||||||
|
return CUBEB_ERROR_INVALID_PARAMETER;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!context->ops->get_min_latency) {
|
||||||
|
return CUBEB_ERROR_NOT_SUPPORTED;
|
||||||
|
}
|
||||||
|
|
||||||
|
return context->ops->get_min_latency(context, params, latency_ms);
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
cubeb_get_preferred_sample_rate(cubeb * context, uint32_t * rate)
|
||||||
|
{
|
||||||
|
if (!context || !rate) {
|
||||||
|
return CUBEB_ERROR_INVALID_PARAMETER;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!context->ops->get_preferred_sample_rate) {
|
||||||
|
return CUBEB_ERROR_NOT_SUPPORTED;
|
||||||
|
}
|
||||||
|
|
||||||
|
return context->ops->get_preferred_sample_rate(context, rate);
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
cubeb_get_preferred_channel_layout(cubeb * context, cubeb_channel_layout * layout)
|
||||||
|
{
|
||||||
|
if (!context || !layout) {
|
||||||
|
return CUBEB_ERROR_INVALID_PARAMETER;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!context->ops->get_preferred_channel_layout) {
|
||||||
|
return CUBEB_ERROR_NOT_SUPPORTED;
|
||||||
|
}
|
||||||
|
|
||||||
|
return context->ops->get_preferred_channel_layout(context, layout);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
cubeb_destroy(cubeb * context)
|
||||||
|
{
|
||||||
|
if (!context) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
context->ops->destroy(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
cubeb_stream_init(cubeb * context, cubeb_stream ** stream, char const * stream_name,
|
||||||
|
cubeb_devid input_device,
|
||||||
|
cubeb_stream_params * input_stream_params,
|
||||||
|
cubeb_devid output_device,
|
||||||
|
cubeb_stream_params * output_stream_params,
|
||||||
|
unsigned int latency,
|
||||||
|
cubeb_data_callback data_callback,
|
||||||
|
cubeb_state_callback state_callback,
|
||||||
|
void * user_ptr)
|
||||||
|
{
|
||||||
|
int r;
|
||||||
|
|
||||||
|
if (!context || !stream) {
|
||||||
|
return CUBEB_ERROR_INVALID_PARAMETER;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((r = validate_stream_params(input_stream_params, output_stream_params)) != CUBEB_OK ||
|
||||||
|
(r = validate_latency(latency)) != CUBEB_OK) {
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
r = context->ops->stream_init(context, stream, stream_name,
|
||||||
|
input_device,
|
||||||
|
input_stream_params,
|
||||||
|
output_device,
|
||||||
|
output_stream_params,
|
||||||
|
latency,
|
||||||
|
data_callback,
|
||||||
|
state_callback,
|
||||||
|
user_ptr);
|
||||||
|
|
||||||
|
if (r == CUBEB_ERROR_INVALID_FORMAT) {
|
||||||
|
LOG("Invalid format, %p %p %d %d",
|
||||||
|
output_stream_params, input_stream_params,
|
||||||
|
output_stream_params && output_stream_params->format,
|
||||||
|
input_stream_params && input_stream_params->format);
|
||||||
|
}
|
||||||
|
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
cubeb_stream_destroy(cubeb_stream * stream)
|
||||||
|
{
|
||||||
|
if (!stream) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
stream->context->ops->stream_destroy(stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
cubeb_stream_start(cubeb_stream * stream)
|
||||||
|
{
|
||||||
|
if (!stream) {
|
||||||
|
return CUBEB_ERROR_INVALID_PARAMETER;
|
||||||
|
}
|
||||||
|
|
||||||
|
return stream->context->ops->stream_start(stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
cubeb_stream_stop(cubeb_stream * stream)
|
||||||
|
{
|
||||||
|
if (!stream) {
|
||||||
|
return CUBEB_ERROR_INVALID_PARAMETER;
|
||||||
|
}
|
||||||
|
|
||||||
|
return stream->context->ops->stream_stop(stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
cubeb_stream_get_position(cubeb_stream * stream, uint64_t * position)
|
||||||
|
{
|
||||||
|
if (!stream || !position) {
|
||||||
|
return CUBEB_ERROR_INVALID_PARAMETER;
|
||||||
|
}
|
||||||
|
|
||||||
|
return stream->context->ops->stream_get_position(stream, position);
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
cubeb_stream_get_latency(cubeb_stream * stream, uint32_t * latency)
|
||||||
|
{
|
||||||
|
if (!stream || !latency) {
|
||||||
|
return CUBEB_ERROR_INVALID_PARAMETER;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!stream->context->ops->stream_get_latency) {
|
||||||
|
return CUBEB_ERROR_NOT_SUPPORTED;
|
||||||
|
}
|
||||||
|
|
||||||
|
return stream->context->ops->stream_get_latency(stream, latency);
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
cubeb_stream_set_volume(cubeb_stream * stream, float volume)
|
||||||
|
{
|
||||||
|
if (!stream || volume > 1.0 || volume < 0.0) {
|
||||||
|
return CUBEB_ERROR_INVALID_PARAMETER;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!stream->context->ops->stream_set_volume) {
|
||||||
|
return CUBEB_ERROR_NOT_SUPPORTED;
|
||||||
|
}
|
||||||
|
|
||||||
|
return stream->context->ops->stream_set_volume(stream, volume);
|
||||||
|
}
|
||||||
|
|
||||||
|
int cubeb_stream_set_panning(cubeb_stream * stream, float panning)
|
||||||
|
{
|
||||||
|
if (!stream || panning < -1.0 || panning > 1.0) {
|
||||||
|
return CUBEB_ERROR_INVALID_PARAMETER;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!stream->context->ops->stream_set_panning) {
|
||||||
|
return CUBEB_ERROR_NOT_SUPPORTED;
|
||||||
|
}
|
||||||
|
|
||||||
|
return stream->context->ops->stream_set_panning(stream, panning);
|
||||||
|
}
|
||||||
|
|
||||||
|
int cubeb_stream_get_current_device(cubeb_stream * stream,
|
||||||
|
cubeb_device ** const device)
|
||||||
|
{
|
||||||
|
if (!stream || !device) {
|
||||||
|
return CUBEB_ERROR_INVALID_PARAMETER;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!stream->context->ops->stream_get_current_device) {
|
||||||
|
return CUBEB_ERROR_NOT_SUPPORTED;
|
||||||
|
}
|
||||||
|
|
||||||
|
return stream->context->ops->stream_get_current_device(stream, device);
|
||||||
|
}
|
||||||
|
|
||||||
|
int cubeb_stream_device_destroy(cubeb_stream * stream,
|
||||||
|
cubeb_device * device)
|
||||||
|
{
|
||||||
|
if (!stream || !device) {
|
||||||
|
return CUBEB_ERROR_INVALID_PARAMETER;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!stream->context->ops->stream_device_destroy) {
|
||||||
|
return CUBEB_ERROR_NOT_SUPPORTED;
|
||||||
|
}
|
||||||
|
|
||||||
|
return stream->context->ops->stream_device_destroy(stream, device);
|
||||||
|
}
|
||||||
|
|
||||||
|
int cubeb_stream_register_device_changed_callback(cubeb_stream * stream,
|
||||||
|
cubeb_device_changed_callback device_changed_callback)
|
||||||
|
{
|
||||||
|
if (!stream) {
|
||||||
|
return CUBEB_ERROR_INVALID_PARAMETER;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!stream->context->ops->stream_register_device_changed_callback) {
|
||||||
|
return CUBEB_ERROR_NOT_SUPPORTED;
|
||||||
|
}
|
||||||
|
|
||||||
|
return stream->context->ops->stream_register_device_changed_callback(stream, device_changed_callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
static
|
||||||
|
void log_device(cubeb_device_info * device_info)
|
||||||
|
{
|
||||||
|
char devfmts[128] = "";
|
||||||
|
const char * devtype, * devstate, * devdeffmt;
|
||||||
|
|
||||||
|
switch (device_info->type) {
|
||||||
|
case CUBEB_DEVICE_TYPE_INPUT:
|
||||||
|
devtype = "input";
|
||||||
|
break;
|
||||||
|
case CUBEB_DEVICE_TYPE_OUTPUT:
|
||||||
|
devtype = "output";
|
||||||
|
break;
|
||||||
|
case CUBEB_DEVICE_TYPE_UNKNOWN:
|
||||||
|
default:
|
||||||
|
devtype = "unknown?";
|
||||||
|
break;
|
||||||
|
};
|
||||||
|
|
||||||
|
switch (device_info->state) {
|
||||||
|
case CUBEB_DEVICE_STATE_DISABLED:
|
||||||
|
devstate = "disabled";
|
||||||
|
break;
|
||||||
|
case CUBEB_DEVICE_STATE_UNPLUGGED:
|
||||||
|
devstate = "unplugged";
|
||||||
|
break;
|
||||||
|
case CUBEB_DEVICE_STATE_ENABLED:
|
||||||
|
devstate = "enabled";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
devstate = "unknown?";
|
||||||
|
break;
|
||||||
|
};
|
||||||
|
|
||||||
|
switch (device_info->default_format) {
|
||||||
|
case CUBEB_DEVICE_FMT_S16LE:
|
||||||
|
devdeffmt = "S16LE";
|
||||||
|
break;
|
||||||
|
case CUBEB_DEVICE_FMT_S16BE:
|
||||||
|
devdeffmt = "S16BE";
|
||||||
|
break;
|
||||||
|
case CUBEB_DEVICE_FMT_F32LE:
|
||||||
|
devdeffmt = "F32LE";
|
||||||
|
break;
|
||||||
|
case CUBEB_DEVICE_FMT_F32BE:
|
||||||
|
devdeffmt = "F32BE";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
devdeffmt = "unknown?";
|
||||||
|
break;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (device_info->format & CUBEB_DEVICE_FMT_S16LE) {
|
||||||
|
strcat(devfmts, " S16LE");
|
||||||
|
}
|
||||||
|
if (device_info->format & CUBEB_DEVICE_FMT_S16BE) {
|
||||||
|
strcat(devfmts, " S16BE");
|
||||||
|
}
|
||||||
|
if (device_info->format & CUBEB_DEVICE_FMT_F32LE) {
|
||||||
|
strcat(devfmts, " F32LE");
|
||||||
|
}
|
||||||
|
if (device_info->format & CUBEB_DEVICE_FMT_F32BE) {
|
||||||
|
strcat(devfmts, " F32BE");
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG("DeviceID: \"%s\"%s\n"
|
||||||
|
"\tName:\t\"%s\"\n"
|
||||||
|
"\tGroup:\t\"%s\"\n"
|
||||||
|
"\tVendor:\t\"%s\"\n"
|
||||||
|
"\tType:\t%s\n"
|
||||||
|
"\tState:\t%s\n"
|
||||||
|
"\tMaximum channels:\t%u\n"
|
||||||
|
"\tFormat:\t%s (0x%x) (default: %s)\n"
|
||||||
|
"\tRate:\t[%u, %u] (default: %u)\n"
|
||||||
|
"\tLatency: lo %u frames, hi %u frames",
|
||||||
|
device_info->device_id, device_info->preferred ? " (PREFERRED)" : "",
|
||||||
|
device_info->friendly_name,
|
||||||
|
device_info->group_id,
|
||||||
|
device_info->vendor_name,
|
||||||
|
devtype,
|
||||||
|
devstate,
|
||||||
|
device_info->max_channels,
|
||||||
|
(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,
|
||||||
|
cubeb_device_type devtype,
|
||||||
|
cubeb_device_collection * collection)
|
||||||
|
{
|
||||||
|
int rv;
|
||||||
|
if ((devtype & (CUBEB_DEVICE_TYPE_INPUT | CUBEB_DEVICE_TYPE_OUTPUT)) == 0)
|
||||||
|
return CUBEB_ERROR_INVALID_PARAMETER;
|
||||||
|
if (collection == NULL)
|
||||||
|
return CUBEB_ERROR_INVALID_PARAMETER;
|
||||||
|
if (!context->ops->enumerate_devices)
|
||||||
|
return CUBEB_ERROR_NOT_SUPPORTED;
|
||||||
|
|
||||||
|
rv = context->ops->enumerate_devices(context, devtype, collection);
|
||||||
|
|
||||||
|
if (g_cubeb_log_callback) {
|
||||||
|
for (size_t i = 0; i < collection->count; i++) {
|
||||||
|
log_device(&collection->device[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return rv;
|
||||||
|
}
|
||||||
|
|
||||||
|
int cubeb_device_collection_destroy(cubeb * context,
|
||||||
|
cubeb_device_collection * collection)
|
||||||
|
{
|
||||||
|
if (context == NULL || collection == NULL)
|
||||||
|
return CUBEB_ERROR_INVALID_PARAMETER;
|
||||||
|
|
||||||
|
if (!context->ops->device_collection_destroy)
|
||||||
|
return CUBEB_ERROR_NOT_SUPPORTED;
|
||||||
|
|
||||||
|
return context->ops->device_collection_destroy(context, collection);
|
||||||
|
}
|
||||||
|
|
||||||
|
int cubeb_register_device_collection_changed(cubeb * context,
|
||||||
|
cubeb_device_type devtype,
|
||||||
|
cubeb_device_collection_changed_callback callback,
|
||||||
|
void * user_ptr)
|
||||||
|
{
|
||||||
|
if (context == NULL || (devtype & (CUBEB_DEVICE_TYPE_INPUT | CUBEB_DEVICE_TYPE_OUTPUT)) == 0)
|
||||||
|
return CUBEB_ERROR_INVALID_PARAMETER;
|
||||||
|
|
||||||
|
if (!context->ops->register_device_collection_changed) {
|
||||||
|
return CUBEB_ERROR_NOT_SUPPORTED;
|
||||||
|
}
|
||||||
|
|
||||||
|
return context->ops->register_device_collection_changed(context, devtype, callback, user_ptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
int cubeb_set_log_callback(cubeb_log_level log_level,
|
||||||
|
cubeb_log_callback log_callback)
|
||||||
|
{
|
||||||
|
if (log_level < CUBEB_LOG_DISABLED || log_level > CUBEB_LOG_VERBOSE) {
|
||||||
|
return CUBEB_ERROR_INVALID_FORMAT;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!log_callback && log_level != CUBEB_LOG_DISABLED) {
|
||||||
|
return CUBEB_ERROR_INVALID_PARAMETER;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (g_cubeb_log_callback && log_callback) {
|
||||||
|
return CUBEB_ERROR_NOT_SUPPORTED;
|
||||||
|
}
|
||||||
|
|
||||||
|
g_cubeb_log_callback = log_callback;
|
||||||
|
g_cubeb_log_level = log_level;
|
||||||
|
|
||||||
|
// Logging a message here allows to initialize the asynchronous logger from a
|
||||||
|
// thread that is not the audio rendering thread, and especially to not
|
||||||
|
// initialize it the first time we find a verbose log, which is often in the
|
||||||
|
// audio rendering callback, that runs from the audio rendering thread, and
|
||||||
|
// that is high priority, and that we don't want to block.
|
||||||
|
if (log_level >= CUBEB_LOG_VERBOSE) {
|
||||||
|
ALOGV("Starting cubeb log");
|
||||||
|
}
|
||||||
|
|
||||||
|
return CUBEB_OK;
|
||||||
|
}
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,97 @@
|
||||||
|
/*
|
||||||
|
* Copyright © 2016 Mozilla Foundation
|
||||||
|
*
|
||||||
|
* This program is made available under an ISC-style license. See the
|
||||||
|
* accompanying file LICENSE for details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef CUBEB_ARRAY_QUEUE_H
|
||||||
|
#define CUBEB_ARRAY_QUEUE_H
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
#include <pthread.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#if defined(__cplusplus)
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
void ** buf;
|
||||||
|
size_t num;
|
||||||
|
size_t writePos;
|
||||||
|
size_t readPos;
|
||||||
|
pthread_mutex_t mutex;
|
||||||
|
} array_queue;
|
||||||
|
|
||||||
|
array_queue * array_queue_create(size_t num)
|
||||||
|
{
|
||||||
|
assert(num != 0);
|
||||||
|
array_queue * new_queue = (array_queue*)calloc(1, sizeof(array_queue));
|
||||||
|
new_queue->buf = (void **)calloc(1, sizeof(void *) * num);
|
||||||
|
new_queue->readPos = 0;
|
||||||
|
new_queue->writePos = 0;
|
||||||
|
new_queue->num = num;
|
||||||
|
|
||||||
|
pthread_mutex_init(&new_queue->mutex, NULL);
|
||||||
|
|
||||||
|
return new_queue;
|
||||||
|
}
|
||||||
|
|
||||||
|
void array_queue_destroy(array_queue * aq)
|
||||||
|
{
|
||||||
|
assert(aq);
|
||||||
|
|
||||||
|
free(aq->buf);
|
||||||
|
pthread_mutex_destroy(&aq->mutex);
|
||||||
|
free(aq);
|
||||||
|
}
|
||||||
|
|
||||||
|
int array_queue_push(array_queue * aq, void * item)
|
||||||
|
{
|
||||||
|
assert(item);
|
||||||
|
|
||||||
|
pthread_mutex_lock(&aq->mutex);
|
||||||
|
int ret = -1;
|
||||||
|
if(aq->buf[aq->writePos % aq->num] == NULL)
|
||||||
|
{
|
||||||
|
aq->buf[aq->writePos % aq->num] = item;
|
||||||
|
aq->writePos = (aq->writePos + 1) % aq->num;
|
||||||
|
ret = 0;
|
||||||
|
}
|
||||||
|
// else queue is full
|
||||||
|
pthread_mutex_unlock(&aq->mutex);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
void* array_queue_pop(array_queue * aq)
|
||||||
|
{
|
||||||
|
pthread_mutex_lock(&aq->mutex);
|
||||||
|
void * value = aq->buf[aq->readPos % aq->num];
|
||||||
|
if(value)
|
||||||
|
{
|
||||||
|
aq->buf[aq->readPos % aq->num] = NULL;
|
||||||
|
aq->readPos = (aq->readPos + 1) % aq->num;
|
||||||
|
}
|
||||||
|
pthread_mutex_unlock(&aq->mutex);
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t array_queue_get_size(array_queue * aq)
|
||||||
|
{
|
||||||
|
pthread_mutex_lock(&aq->mutex);
|
||||||
|
ssize_t r = aq->writePos - aq->readPos;
|
||||||
|
if (r < 0) {
|
||||||
|
r = aq->num + r;
|
||||||
|
assert(r >= 0);
|
||||||
|
}
|
||||||
|
pthread_mutex_unlock(&aq->mutex);
|
||||||
|
return (size_t)r;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if defined(__cplusplus)
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif //CUBE_ARRAY_QUEUE_H
|
|
@ -0,0 +1,26 @@
|
||||||
|
/*
|
||||||
|
* Copyright © 2017 Mozilla Foundation
|
||||||
|
*
|
||||||
|
* This program is made available under an ISC-style license. See the
|
||||||
|
* accompanying file LICENSE for details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef CUBEB_ASSERT
|
||||||
|
#define CUBEB_ASSERT
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This allow using an external release assert method. This file should only
|
||||||
|
* export a function or macro called XASSERT that aborts the program.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define XASSERT(expr) do { \
|
||||||
|
if (!(expr)) { \
|
||||||
|
fprintf(stderr, "%s:%d - fatal error: %s\n", __FILE__, __LINE__, #expr); \
|
||||||
|
abort(); \
|
||||||
|
} \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
#endif
|
|
@ -0,0 +1,440 @@
|
||||||
|
/*
|
||||||
|
* Copyright © 2013 Mozilla Foundation
|
||||||
|
*
|
||||||
|
* This program is made available under an ISC-style license. See the
|
||||||
|
* accompanying file LICENSE for details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#if !defined(NDEBUG)
|
||||||
|
#define NDEBUG
|
||||||
|
#endif
|
||||||
|
#include <assert.h>
|
||||||
|
#include <pthread.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <time.h>
|
||||||
|
#include <dlfcn.h>
|
||||||
|
#include <android/log.h>
|
||||||
|
|
||||||
|
#include "cubeb/cubeb.h"
|
||||||
|
#include "cubeb-internal.h"
|
||||||
|
#include "android/audiotrack_definitions.h"
|
||||||
|
|
||||||
|
#ifndef ALOG
|
||||||
|
#if defined(DEBUG) || defined(FORCE_ALOG)
|
||||||
|
#define ALOG(args...) __android_log_print(ANDROID_LOG_INFO, "Gecko - Cubeb" , ## args)
|
||||||
|
#else
|
||||||
|
#define ALOG(args...)
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A lot of bytes for safety. It should be possible to bring this down a bit. */
|
||||||
|
#define SIZE_AUDIOTRACK_INSTANCE 256
|
||||||
|
|
||||||
|
/**
|
||||||
|
* call dlsym to get the symbol |mangled_name|, handle the error and store the
|
||||||
|
* pointer in |pointer|. Because depending on Android version, we want different
|
||||||
|
* symbols, not finding a symbol is not an error. */
|
||||||
|
#define DLSYM_DLERROR(mangled_name, pointer, lib) \
|
||||||
|
do { \
|
||||||
|
pointer = dlsym(lib, mangled_name); \
|
||||||
|
if (!pointer) { \
|
||||||
|
ALOG("error while loading %stm: %stm\n", mangled_name, dlerror()); \
|
||||||
|
} else { \
|
||||||
|
ALOG("%stm: OK", mangled_name); \
|
||||||
|
} \
|
||||||
|
} while(0);
|
||||||
|
|
||||||
|
static struct cubeb_ops const audiotrack_ops;
|
||||||
|
void audiotrack_destroy(cubeb * context);
|
||||||
|
void audiotrack_stream_destroy(cubeb_stream * stream);
|
||||||
|
|
||||||
|
struct AudioTrack {
|
||||||
|
/* only available on ICS and later. The second int paramter is in fact of type 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
|
||||||
|
* can get the minimum frame count with this signature, and we are
|
||||||
|
* running gingerbread. */
|
||||||
|
/* static */ status_t (*get_min_frame_count_gingerbread)(int* frame_count, 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 (*start)(void* instance);
|
||||||
|
void (*pause)(void* instance);
|
||||||
|
uint32_t (*latency)(void* instance);
|
||||||
|
status_t (*check)(void* instance);
|
||||||
|
status_t (*get_position)(void* instance, uint32_t* position);
|
||||||
|
/* static */ int (*get_output_samplingrate)(int* samplerate, int stream);
|
||||||
|
status_t (*set_marker_position)(void* instance, unsigned int);
|
||||||
|
status_t (*set_volume)(void* instance, float left, float right);
|
||||||
|
};
|
||||||
|
|
||||||
|
struct cubeb {
|
||||||
|
struct cubeb_ops const * ops;
|
||||||
|
void * library;
|
||||||
|
struct AudioTrack klass;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct cubeb_stream {
|
||||||
|
cubeb * context;
|
||||||
|
cubeb_stream_params params;
|
||||||
|
cubeb_data_callback data_callback;
|
||||||
|
cubeb_state_callback state_callback;
|
||||||
|
void * instance;
|
||||||
|
void * user_ptr;
|
||||||
|
/* Number of frames that have been passed to the AudioTrack callback */
|
||||||
|
long unsigned written;
|
||||||
|
int draining;
|
||||||
|
};
|
||||||
|
|
||||||
|
static void
|
||||||
|
audiotrack_refill(int event, void* user, void* info)
|
||||||
|
{
|
||||||
|
cubeb_stream * stream = user;
|
||||||
|
switch (event) {
|
||||||
|
case EVENT_MORE_DATA: {
|
||||||
|
long got = 0;
|
||||||
|
struct Buffer * b = (struct Buffer*)info;
|
||||||
|
|
||||||
|
if (stream->draining) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
got = stream->data_callback(stream, stream->user_ptr, NULL, b->raw, b->frameCount);
|
||||||
|
|
||||||
|
stream->written += got;
|
||||||
|
|
||||||
|
if (got != (long)b->frameCount) {
|
||||||
|
stream->draining = 1;
|
||||||
|
/* set a marker so we are notified when the are done draining, that is,
|
||||||
|
* when every frame has been played by android. */
|
||||||
|
stream->context->klass.set_marker_position(stream->instance, stream->written);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case EVENT_UNDERRUN:
|
||||||
|
ALOG("underrun in cubeb backend.");
|
||||||
|
break;
|
||||||
|
case EVENT_LOOP_END:
|
||||||
|
assert(0 && "We don't support the loop feature of audiotrack.");
|
||||||
|
break;
|
||||||
|
case EVENT_MARKER:
|
||||||
|
assert(stream->draining);
|
||||||
|
stream->state_callback(stream, stream->user_ptr, CUBEB_STATE_DRAINED);
|
||||||
|
break;
|
||||||
|
case EVENT_NEW_POS:
|
||||||
|
assert(0 && "We don't support the setPositionUpdatePeriod feature of audiotrack.");
|
||||||
|
break;
|
||||||
|
case EVENT_BUFFER_END:
|
||||||
|
assert(0 && "Should not happen.");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* We are running on gingerbread if we found the gingerbread signature for
|
||||||
|
* getMinFrameCount */
|
||||||
|
static int
|
||||||
|
audiotrack_version_is_gingerbread(cubeb * ctx)
|
||||||
|
{
|
||||||
|
return ctx->klass.get_min_frame_count_gingerbread != NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
audiotrack_get_min_frame_count(cubeb * ctx, cubeb_stream_params * params, int * min_frame_count)
|
||||||
|
{
|
||||||
|
status_t status;
|
||||||
|
/* Recent Android have a getMinFrameCount method. */
|
||||||
|
if (!audiotrack_version_is_gingerbread(ctx)) {
|
||||||
|
status = ctx->klass.get_min_frame_count(min_frame_count, params->stream_type, params->rate);
|
||||||
|
} else {
|
||||||
|
status = ctx->klass.get_min_frame_count_gingerbread(min_frame_count, params->stream_type, params->rate);
|
||||||
|
}
|
||||||
|
if (status != 0) {
|
||||||
|
ALOG("error getting the min frame count");
|
||||||
|
return CUBEB_ERROR;
|
||||||
|
}
|
||||||
|
return CUBEB_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
audiotrack_init(cubeb ** context, char const * context_name)
|
||||||
|
{
|
||||||
|
cubeb * ctx;
|
||||||
|
struct AudioTrack* c;
|
||||||
|
|
||||||
|
assert(context);
|
||||||
|
*context = NULL;
|
||||||
|
|
||||||
|
ctx = calloc(1, sizeof(*ctx));
|
||||||
|
assert(ctx);
|
||||||
|
|
||||||
|
/* If we use an absolute path here ("/system/lib/libmedia.so"), and on Android
|
||||||
|
* 2.2, the dlopen succeeds, all the dlsym succeed, but a segfault happens on
|
||||||
|
* the first call to a dlsym'ed function. Somehow this does not happen when
|
||||||
|
* using only the name of the library. */
|
||||||
|
ctx->library = dlopen("libmedia.so", RTLD_LAZY);
|
||||||
|
if (!ctx->library) {
|
||||||
|
ALOG("dlopen error: %s.", dlerror());
|
||||||
|
free(ctx);
|
||||||
|
return CUBEB_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Recent Android first, then Gingerbread. */
|
||||||
|
DLSYM_DLERROR("_ZN7android10AudioTrackC1EijiiijPFviPvS1_ES1_ii", ctx->klass.ctor, ctx->library);
|
||||||
|
DLSYM_DLERROR("_ZN7android10AudioTrackD1Ev", ctx->klass.dtor, ctx->library);
|
||||||
|
|
||||||
|
DLSYM_DLERROR("_ZNK7android10AudioTrack7latencyEv", ctx->klass.latency, ctx->library);
|
||||||
|
DLSYM_DLERROR("_ZNK7android10AudioTrack9initCheckEv", ctx->klass.check, ctx->library);
|
||||||
|
|
||||||
|
DLSYM_DLERROR("_ZN7android11AudioSystem21getOutputSamplingRateEPii", ctx->klass.get_output_samplingrate, ctx->library);
|
||||||
|
|
||||||
|
/* |getMinFrameCount| is available on gingerbread and ICS with different signatures. */
|
||||||
|
DLSYM_DLERROR("_ZN7android10AudioTrack16getMinFrameCountEPi19audio_stream_type_tj", ctx->klass.get_min_frame_count, ctx->library);
|
||||||
|
if (!ctx->klass.get_min_frame_count) {
|
||||||
|
DLSYM_DLERROR("_ZN7android10AudioTrack16getMinFrameCountEPiij", ctx->klass.get_min_frame_count_gingerbread, ctx->library);
|
||||||
|
}
|
||||||
|
|
||||||
|
DLSYM_DLERROR("_ZN7android10AudioTrack5startEv", ctx->klass.start, ctx->library);
|
||||||
|
DLSYM_DLERROR("_ZN7android10AudioTrack5pauseEv", ctx->klass.pause, 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 */
|
||||||
|
c = &ctx->klass;
|
||||||
|
if(!(c->ctor &&
|
||||||
|
c->dtor && c->latency && c->check &&
|
||||||
|
/* at least one way to get the minimum frame count to request. */
|
||||||
|
(c->get_min_frame_count ||
|
||||||
|
c->get_min_frame_count_gingerbread) &&
|
||||||
|
c->start && c->pause && c->get_position && c->set_marker_position)) {
|
||||||
|
ALOG("Could not find all the symbols we need.");
|
||||||
|
audiotrack_destroy(ctx);
|
||||||
|
return CUBEB_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx->ops = &audiotrack_ops;
|
||||||
|
|
||||||
|
*context = ctx;
|
||||||
|
|
||||||
|
return CUBEB_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
char const *
|
||||||
|
audiotrack_get_backend_id(cubeb * context)
|
||||||
|
{
|
||||||
|
return "audiotrack";
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
audiotrack_get_max_channel_count(cubeb * ctx, uint32_t * max_channels)
|
||||||
|
{
|
||||||
|
assert(ctx && max_channels);
|
||||||
|
|
||||||
|
/* The android mixer handles up to two channels, see
|
||||||
|
http://androidxref.com/4.2.2_r1/xref/frameworks/av/services/audioflinger/AudioFlinger.h#67 */
|
||||||
|
*max_channels = 2;
|
||||||
|
|
||||||
|
return CUBEB_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
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
|
||||||
|
* audiotrack_stream_init), so this value is not going to be used. */
|
||||||
|
int r;
|
||||||
|
|
||||||
|
r = audiotrack_get_min_frame_count(ctx, ¶ms, (int *)latency_ms);
|
||||||
|
if (r != CUBEB_OK) {
|
||||||
|
return CUBEB_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
return CUBEB_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
audiotrack_get_preferred_sample_rate(cubeb * ctx, uint32_t * rate)
|
||||||
|
{
|
||||||
|
status_t r;
|
||||||
|
|
||||||
|
r = ctx->klass.get_output_samplingrate((int32_t *)rate, 3 /* MUSIC */);
|
||||||
|
|
||||||
|
return r == 0 ? CUBEB_OK : CUBEB_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
audiotrack_destroy(cubeb * context)
|
||||||
|
{
|
||||||
|
assert(context);
|
||||||
|
|
||||||
|
dlclose(context->library);
|
||||||
|
|
||||||
|
free(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
audiotrack_stream_init(cubeb * ctx, cubeb_stream ** stream, char const * stream_name,
|
||||||
|
cubeb_devid input_device,
|
||||||
|
cubeb_stream_params * input_stream_params,
|
||||||
|
cubeb_devid output_device,
|
||||||
|
cubeb_stream_params * output_stream_params,
|
||||||
|
unsigned int latency,
|
||||||
|
cubeb_data_callback data_callback,
|
||||||
|
cubeb_state_callback state_callback,
|
||||||
|
void * user_ptr)
|
||||||
|
{
|
||||||
|
cubeb_stream * stm;
|
||||||
|
int32_t channels;
|
||||||
|
uint32_t min_frame_count;
|
||||||
|
|
||||||
|
assert(ctx && stream);
|
||||||
|
|
||||||
|
assert(!input_stream_params && "not supported");
|
||||||
|
if (input_device || output_device) {
|
||||||
|
/* Device selection not yet implemented. */
|
||||||
|
return CUBEB_ERROR_DEVICE_UNAVAILABLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (output_stream_params->format == CUBEB_SAMPLE_FLOAT32LE ||
|
||||||
|
output_stream_params->format == CUBEB_SAMPLE_FLOAT32BE) {
|
||||||
|
return CUBEB_ERROR_INVALID_FORMAT;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (audiotrack_get_min_frame_count(ctx, output_stream_params, (int *)&min_frame_count)) {
|
||||||
|
return CUBEB_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
stm = calloc(1, sizeof(*stm));
|
||||||
|
assert(stm);
|
||||||
|
|
||||||
|
stm->context = ctx;
|
||||||
|
stm->data_callback = data_callback;
|
||||||
|
stm->state_callback = state_callback;
|
||||||
|
stm->user_ptr = user_ptr;
|
||||||
|
stm->params = *output_stream_params;
|
||||||
|
|
||||||
|
stm->instance = calloc(SIZE_AUDIOTRACK_INSTANCE, 1);
|
||||||
|
(*(uint32_t*)((intptr_t)stm->instance + SIZE_AUDIOTRACK_INSTANCE - 4)) = 0xbaadbaad;
|
||||||
|
assert(stm->instance && "cubeb: EOM");
|
||||||
|
|
||||||
|
/* gingerbread uses old channel layout enum */
|
||||||
|
if (audiotrack_version_is_gingerbread(ctx)) {
|
||||||
|
channels = stm->params.channels == 2 ? AUDIO_CHANNEL_OUT_STEREO_Legacy : AUDIO_CHANNEL_OUT_MONO_Legacy;
|
||||||
|
} else {
|
||||||
|
channels = stm->params.channels == 2 ? AUDIO_CHANNEL_OUT_STEREO_ICS : AUDIO_CHANNEL_OUT_MONO_ICS;
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx->klass.ctor(stm->instance, stm->params.stream_type, stm->params.rate,
|
||||||
|
AUDIO_FORMAT_PCM_16_BIT, channels, min_frame_count, 0,
|
||||||
|
audiotrack_refill, stm, 0, 0);
|
||||||
|
|
||||||
|
assert((*(uint32_t*)((intptr_t)stm->instance + SIZE_AUDIOTRACK_INSTANCE - 4)) == 0xbaadbaad);
|
||||||
|
|
||||||
|
if (ctx->klass.check(stm->instance)) {
|
||||||
|
ALOG("stream not initialized properly.");
|
||||||
|
audiotrack_stream_destroy(stm);
|
||||||
|
return CUBEB_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
*stream = stm;
|
||||||
|
|
||||||
|
return CUBEB_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
audiotrack_stream_destroy(cubeb_stream * stream)
|
||||||
|
{
|
||||||
|
assert(stream->context);
|
||||||
|
|
||||||
|
stream->context->klass.dtor(stream->instance);
|
||||||
|
|
||||||
|
free(stream->instance);
|
||||||
|
stream->instance = NULL;
|
||||||
|
free(stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
audiotrack_stream_start(cubeb_stream * stream)
|
||||||
|
{
|
||||||
|
assert(stream->instance);
|
||||||
|
|
||||||
|
stream->context->klass.start(stream->instance);
|
||||||
|
stream->state_callback(stream, stream->user_ptr, CUBEB_STATE_STARTED);
|
||||||
|
|
||||||
|
return CUBEB_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
audiotrack_stream_stop(cubeb_stream * stream)
|
||||||
|
{
|
||||||
|
assert(stream->instance);
|
||||||
|
|
||||||
|
stream->context->klass.pause(stream->instance);
|
||||||
|
stream->state_callback(stream, stream->user_ptr, CUBEB_STATE_STOPPED);
|
||||||
|
|
||||||
|
return CUBEB_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
audiotrack_stream_get_position(cubeb_stream * stream, uint64_t * position)
|
||||||
|
{
|
||||||
|
uint32_t p;
|
||||||
|
|
||||||
|
assert(stream->instance && position);
|
||||||
|
stream->context->klass.get_position(stream->instance, &p);
|
||||||
|
*position = p;
|
||||||
|
|
||||||
|
return CUBEB_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
audiotrack_stream_get_latency(cubeb_stream * stream, uint32_t * latency)
|
||||||
|
{
|
||||||
|
assert(stream->instance && latency);
|
||||||
|
|
||||||
|
/* Android returns the latency in ms, we want it in frames. */
|
||||||
|
*latency = stream->context->klass.latency(stream->instance);
|
||||||
|
/* with rate <= 96000, we won't overflow until 44.739 seconds of latency */
|
||||||
|
*latency = (*latency * stream->params.rate) / 1000;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
audiotrack_stream_set_volume(cubeb_stream * stream, float volume)
|
||||||
|
{
|
||||||
|
status_t status;
|
||||||
|
|
||||||
|
status = stream->context->klass.set_volume(stream->instance, volume, volume);
|
||||||
|
|
||||||
|
if (status) {
|
||||||
|
return CUBEB_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
return CUBEB_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct cubeb_ops const audiotrack_ops = {
|
||||||
|
.init = audiotrack_init,
|
||||||
|
.get_backend_id = audiotrack_get_backend_id,
|
||||||
|
.get_max_channel_count = audiotrack_get_max_channel_count,
|
||||||
|
.get_min_latency = audiotrack_get_min_latency,
|
||||||
|
.get_preferred_sample_rate = audiotrack_get_preferred_sample_rate,
|
||||||
|
.get_preferred_channel_layout = NULL,
|
||||||
|
.enumerate_devices = NULL,
|
||||||
|
.device_collection_destroy = NULL,
|
||||||
|
.destroy = audiotrack_destroy,
|
||||||
|
.stream_init = audiotrack_stream_init,
|
||||||
|
.stream_destroy = audiotrack_stream_destroy,
|
||||||
|
.stream_start = audiotrack_stream_start,
|
||||||
|
.stream_stop = audiotrack_stream_stop,
|
||||||
|
.stream_get_position = audiotrack_stream_get_position,
|
||||||
|
.stream_get_latency = audiotrack_stream_get_latency,
|
||||||
|
.stream_set_volume = audiotrack_stream_set_volume,
|
||||||
|
.stream_set_panning = NULL,
|
||||||
|
.stream_get_current_device = NULL,
|
||||||
|
.stream_device_destroy = NULL,
|
||||||
|
.stream_register_device_changed_callback = NULL,
|
||||||
|
.register_device_collection_changed = NULL
|
||||||
|
};
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,362 @@
|
||||||
|
/*
|
||||||
|
* Copyright © 2015 Mozilla Foundation
|
||||||
|
*
|
||||||
|
* This program is made available under an ISC-style license. See the
|
||||||
|
* accompanying file LICENSE for details.
|
||||||
|
*/
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <math.h>
|
||||||
|
#include <sys/fmutex.h>
|
||||||
|
|
||||||
|
#include <kai.h>
|
||||||
|
|
||||||
|
#include "cubeb/cubeb.h"
|
||||||
|
#include "cubeb-internal.h"
|
||||||
|
|
||||||
|
/* We don't support more than 2 channels in KAI */
|
||||||
|
#define MAX_CHANNELS 2
|
||||||
|
|
||||||
|
#define NBUFS 2
|
||||||
|
#define FRAME_SIZE 2048
|
||||||
|
|
||||||
|
struct cubeb_stream_item {
|
||||||
|
cubeb_stream * stream;
|
||||||
|
};
|
||||||
|
|
||||||
|
static struct cubeb_ops const kai_ops;
|
||||||
|
|
||||||
|
struct cubeb {
|
||||||
|
struct cubeb_ops const * ops;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct cubeb_stream {
|
||||||
|
cubeb * context;
|
||||||
|
cubeb_stream_params params;
|
||||||
|
cubeb_data_callback data_callback;
|
||||||
|
cubeb_state_callback state_callback;
|
||||||
|
void * user_ptr;
|
||||||
|
|
||||||
|
HKAI hkai;
|
||||||
|
KAISPEC spec;
|
||||||
|
uint64_t total_frames;
|
||||||
|
float soft_volume;
|
||||||
|
_fmutex mutex;
|
||||||
|
float float_buffer[FRAME_SIZE * MAX_CHANNELS];
|
||||||
|
};
|
||||||
|
|
||||||
|
static inline long
|
||||||
|
frames_to_bytes(long frames, cubeb_stream_params params)
|
||||||
|
{
|
||||||
|
return frames * 2 * params.channels; /* 2 bytes per frame */
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline long
|
||||||
|
bytes_to_frames(long bytes, cubeb_stream_params params)
|
||||||
|
{
|
||||||
|
return bytes / 2 / params.channels; /* 2 bytes per frame */
|
||||||
|
}
|
||||||
|
|
||||||
|
static void kai_destroy(cubeb * ctx);
|
||||||
|
|
||||||
|
/*static*/ int
|
||||||
|
kai_init(cubeb ** context, char const * context_name)
|
||||||
|
{
|
||||||
|
cubeb * ctx;
|
||||||
|
|
||||||
|
XASSERT(context);
|
||||||
|
*context = NULL;
|
||||||
|
|
||||||
|
if (kaiInit(KAIM_AUTO))
|
||||||
|
return CUBEB_ERROR;
|
||||||
|
|
||||||
|
ctx = calloc(1, sizeof(*ctx));
|
||||||
|
XASSERT(ctx);
|
||||||
|
|
||||||
|
ctx->ops = &kai_ops;
|
||||||
|
|
||||||
|
*context = ctx;
|
||||||
|
|
||||||
|
return CUBEB_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
static char const *
|
||||||
|
kai_get_backend_id(cubeb * ctx)
|
||||||
|
{
|
||||||
|
return "kai";
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
kai_destroy(cubeb * ctx)
|
||||||
|
{
|
||||||
|
kaiDone();
|
||||||
|
|
||||||
|
free(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
float_to_s16ne(int16_t *dst, float *src, size_t n)
|
||||||
|
{
|
||||||
|
long l;
|
||||||
|
|
||||||
|
while (n--) {
|
||||||
|
l = lrintf(*src++ * 0x8000);
|
||||||
|
if (l > 32767)
|
||||||
|
l = 32767;
|
||||||
|
if (l < -32768)
|
||||||
|
l = -32768;
|
||||||
|
*dst++ = (int16_t)l;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static ULONG APIENTRY
|
||||||
|
kai_callback(PVOID cbdata, PVOID buffer, ULONG len)
|
||||||
|
{
|
||||||
|
cubeb_stream * stm = cbdata;
|
||||||
|
void *p;
|
||||||
|
long wanted_frames;
|
||||||
|
long frames;
|
||||||
|
float soft_volume;
|
||||||
|
int elements = len / sizeof(int16_t);
|
||||||
|
|
||||||
|
p = stm->params.format == CUBEB_SAMPLE_FLOAT32NE
|
||||||
|
? stm->float_buffer : buffer;
|
||||||
|
|
||||||
|
wanted_frames = bytes_to_frames(len, stm->params);
|
||||||
|
frames = stm->data_callback(stm, stm->user_ptr, NULL, p, wanted_frames);
|
||||||
|
|
||||||
|
_fmutex_request(&stm->mutex, 0);
|
||||||
|
stm->total_frames += frames;
|
||||||
|
soft_volume = stm->soft_volume;
|
||||||
|
_fmutex_release(&stm->mutex);
|
||||||
|
|
||||||
|
if (frames < wanted_frames)
|
||||||
|
stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_DRAINED);
|
||||||
|
|
||||||
|
if (stm->params.format == CUBEB_SAMPLE_FLOAT32NE)
|
||||||
|
float_to_s16ne(buffer, p, elements);
|
||||||
|
|
||||||
|
if (soft_volume != -1.0f) {
|
||||||
|
int16_t *b = buffer;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
for (i = 0; i < elements; i++)
|
||||||
|
*b++ *= soft_volume;
|
||||||
|
}
|
||||||
|
|
||||||
|
return frames_to_bytes(frames, stm->params);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void kai_stream_destroy(cubeb_stream * stm);
|
||||||
|
|
||||||
|
static int
|
||||||
|
kai_stream_init(cubeb * context, cubeb_stream ** stream,
|
||||||
|
char const * stream_name,
|
||||||
|
cubeb_devid input_device,
|
||||||
|
cubeb_stream_params * input_stream_params,
|
||||||
|
cubeb_devid output_device,
|
||||||
|
cubeb_stream_params * output_stream_params,
|
||||||
|
unsigned int latency, cubeb_data_callback data_callback,
|
||||||
|
cubeb_state_callback state_callback, void * user_ptr)
|
||||||
|
{
|
||||||
|
cubeb_stream * stm;
|
||||||
|
KAISPEC wanted_spec;
|
||||||
|
|
||||||
|
XASSERT(!input_stream_params && "not supported.");
|
||||||
|
if (input_device || output_device) {
|
||||||
|
/* Device selection not yet implemented. */
|
||||||
|
return CUBEB_ERROR_DEVICE_UNAVAILABLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!output_stream_params)
|
||||||
|
return CUBEB_ERROR_INVALID_PARAMETER;
|
||||||
|
|
||||||
|
if (output_stream_params->channels < 1 ||
|
||||||
|
output_stream_params->channels > MAX_CHANNELS)
|
||||||
|
return CUBEB_ERROR_INVALID_FORMAT;
|
||||||
|
|
||||||
|
XASSERT(context);
|
||||||
|
XASSERT(stream);
|
||||||
|
|
||||||
|
*stream = NULL;
|
||||||
|
|
||||||
|
stm = calloc(1, sizeof(*stm));
|
||||||
|
XASSERT(stm);
|
||||||
|
|
||||||
|
stm->context = context;
|
||||||
|
stm->params = *output_stream_params;
|
||||||
|
stm->data_callback = data_callback;
|
||||||
|
stm->state_callback = state_callback;
|
||||||
|
stm->user_ptr = user_ptr;
|
||||||
|
stm->soft_volume = -1.0f;
|
||||||
|
|
||||||
|
if (_fmutex_create(&stm->mutex, 0)) {
|
||||||
|
free(stm);
|
||||||
|
return CUBEB_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
wanted_spec.usDeviceIndex = 0;
|
||||||
|
wanted_spec.ulType = KAIT_PLAY;
|
||||||
|
wanted_spec.ulBitsPerSample = BPS_16;
|
||||||
|
wanted_spec.ulSamplingRate = stm->params.rate;
|
||||||
|
wanted_spec.ulDataFormat = MCI_WAVE_FORMAT_PCM;
|
||||||
|
wanted_spec.ulChannels = stm->params.channels;
|
||||||
|
wanted_spec.ulNumBuffers = NBUFS;
|
||||||
|
wanted_spec.ulBufferSize = frames_to_bytes(FRAME_SIZE, stm->params);
|
||||||
|
wanted_spec.fShareable = TRUE;
|
||||||
|
wanted_spec.pfnCallBack = kai_callback;
|
||||||
|
wanted_spec.pCallBackData = stm;
|
||||||
|
|
||||||
|
if (kaiOpen(&wanted_spec, &stm->spec, &stm->hkai)) {
|
||||||
|
_fmutex_close(&stm->mutex);
|
||||||
|
free(stm);
|
||||||
|
return CUBEB_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
*stream = stm;
|
||||||
|
|
||||||
|
return CUBEB_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
kai_stream_destroy(cubeb_stream * stm)
|
||||||
|
{
|
||||||
|
kaiClose(stm->hkai);
|
||||||
|
_fmutex_close(&stm->mutex);
|
||||||
|
free(stm);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
kai_get_max_channel_count(cubeb * ctx, uint32_t * max_channels)
|
||||||
|
{
|
||||||
|
XASSERT(ctx && max_channels);
|
||||||
|
|
||||||
|
*max_channels = MAX_CHANNELS;
|
||||||
|
|
||||||
|
return CUBEB_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
kai_get_min_latency(cubeb * ctx, cubeb_stream_params params, uint32_t * latency)
|
||||||
|
{
|
||||||
|
/* We have at least two buffers. One is being played, the other one is being
|
||||||
|
filled. So there is as much latency as one buffer. */
|
||||||
|
*latency = FRAME_SIZE;
|
||||||
|
|
||||||
|
return CUBEB_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
kai_get_preferred_sample_rate(cubeb * ctx, uint32_t * rate)
|
||||||
|
{
|
||||||
|
cubeb_stream_params params;
|
||||||
|
KAISPEC wanted_spec;
|
||||||
|
KAISPEC spec;
|
||||||
|
HKAI hkai;
|
||||||
|
|
||||||
|
params.format = CUBEB_SAMPLE_S16NE;
|
||||||
|
params.rate = 48000;
|
||||||
|
params.channels = 2;
|
||||||
|
|
||||||
|
wanted_spec.usDeviceIndex = 0;
|
||||||
|
wanted_spec.ulType = KAIT_PLAY;
|
||||||
|
wanted_spec.ulBitsPerSample = BPS_16;
|
||||||
|
wanted_spec.ulSamplingRate = params.rate;
|
||||||
|
wanted_spec.ulDataFormat = MCI_WAVE_FORMAT_PCM;
|
||||||
|
wanted_spec.ulChannels = params.channels;
|
||||||
|
wanted_spec.ulNumBuffers = NBUFS;
|
||||||
|
wanted_spec.ulBufferSize = frames_to_bytes(FRAME_SIZE, params);
|
||||||
|
wanted_spec.fShareable = TRUE;
|
||||||
|
wanted_spec.pfnCallBack = kai_callback;
|
||||||
|
wanted_spec.pCallBackData = NULL;
|
||||||
|
|
||||||
|
/* Test 48KHz */
|
||||||
|
if (kaiOpen(&wanted_spec, &spec, &hkai)) {
|
||||||
|
/* Not supported. Fall back to 44.1KHz */
|
||||||
|
params.rate = 44100;
|
||||||
|
} else {
|
||||||
|
/* Supported. Use 48KHz */
|
||||||
|
kaiClose(hkai);
|
||||||
|
}
|
||||||
|
|
||||||
|
*rate = params.rate;
|
||||||
|
|
||||||
|
return CUBEB_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
kai_stream_start(cubeb_stream * stm)
|
||||||
|
{
|
||||||
|
if (kaiPlay(stm->hkai))
|
||||||
|
return CUBEB_ERROR;
|
||||||
|
|
||||||
|
stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STARTED);
|
||||||
|
|
||||||
|
return CUBEB_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
kai_stream_stop(cubeb_stream * stm)
|
||||||
|
{
|
||||||
|
if (kaiStop(stm->hkai))
|
||||||
|
return CUBEB_ERROR;
|
||||||
|
|
||||||
|
stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STOPPED);
|
||||||
|
|
||||||
|
return CUBEB_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
kai_stream_get_position(cubeb_stream * stm, uint64_t * position)
|
||||||
|
{
|
||||||
|
_fmutex_request(&stm->mutex, 0);
|
||||||
|
*position = stm->total_frames;
|
||||||
|
_fmutex_release(&stm->mutex);
|
||||||
|
|
||||||
|
return CUBEB_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
kai_stream_get_latency(cubeb_stream * stm, uint32_t * latency)
|
||||||
|
{
|
||||||
|
/* Out of buffers, one is being played, the others are being filled.
|
||||||
|
So there is as much latency as total buffers - 1. */
|
||||||
|
*latency = bytes_to_frames(stm->spec.ulBufferSize, stm->params)
|
||||||
|
* (stm->spec.ulNumBuffers - 1);
|
||||||
|
|
||||||
|
return CUBEB_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
kai_stream_set_volume(cubeb_stream * stm, float volume)
|
||||||
|
{
|
||||||
|
_fmutex_request(&stm->mutex, 0);
|
||||||
|
stm->soft_volume = volume;
|
||||||
|
_fmutex_release(&stm->mutex);
|
||||||
|
|
||||||
|
return CUBEB_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct cubeb_ops const kai_ops = {
|
||||||
|
/*.init =*/ kai_init,
|
||||||
|
/*.get_backend_id =*/ kai_get_backend_id,
|
||||||
|
/*.get_max_channel_count=*/ kai_get_max_channel_count,
|
||||||
|
/*.get_min_latency=*/ kai_get_min_latency,
|
||||||
|
/*.get_preferred_sample_rate =*/ kai_get_preferred_sample_rate,
|
||||||
|
/*.get_preferred_channel_layout =*/ NULL,
|
||||||
|
/*.enumerate_devices =*/ NULL,
|
||||||
|
/*.device_collection_destroy =*/ NULL,
|
||||||
|
/*.destroy =*/ kai_destroy,
|
||||||
|
/*.stream_init =*/ kai_stream_init,
|
||||||
|
/*.stream_destroy =*/ kai_stream_destroy,
|
||||||
|
/*.stream_start =*/ kai_stream_start,
|
||||||
|
/*.stream_stop =*/ kai_stream_stop,
|
||||||
|
/*.stream_get_position =*/ kai_stream_get_position,
|
||||||
|
/*.stream_get_latency = */ kai_stream_get_latency,
|
||||||
|
/*.stream_set_volume =*/ kai_stream_set_volume,
|
||||||
|
/*.stream_set_panning =*/ NULL,
|
||||||
|
/*.stream_get_current_device =*/ NULL,
|
||||||
|
/*.stream_device_destroy =*/ NULL,
|
||||||
|
/*.stream_register_device_changed_callback=*/ NULL,
|
||||||
|
/*.register_device_collection_changed=*/ NULL
|
||||||
|
};
|
|
@ -0,0 +1,130 @@
|
||||||
|
/*
|
||||||
|
* Copyright © 2016 Mozilla Foundation
|
||||||
|
*
|
||||||
|
* This program is made available under an ISC-style license. See the
|
||||||
|
* accompanying file LICENSE for details.
|
||||||
|
*/
|
||||||
|
#define NOMINMAX
|
||||||
|
|
||||||
|
#include "cubeb_log.h"
|
||||||
|
#include "cubeb_ringbuffer.h"
|
||||||
|
#include <cstdarg>
|
||||||
|
#ifdef _WIN32
|
||||||
|
#include <windows.h>
|
||||||
|
#else
|
||||||
|
#include <time.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
cubeb_log_level g_cubeb_log_level;
|
||||||
|
cubeb_log_callback g_cubeb_log_callback;
|
||||||
|
|
||||||
|
/** The maximum size of a log message, after having been formatted. */
|
||||||
|
const size_t CUBEB_LOG_MESSAGE_MAX_SIZE = 256;
|
||||||
|
/** The maximum number of log messages that can be queued before dropping
|
||||||
|
* messages. */
|
||||||
|
const size_t CUBEB_LOG_MESSAGE_QUEUE_DEPTH = 40;
|
||||||
|
/** Number of milliseconds to wait before dequeuing log messages. */
|
||||||
|
#define CUBEB_LOG_BATCH_PRINT_INTERVAL_MS 10
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This wraps an inline buffer, that represents a log message, that must be
|
||||||
|
* null-terminated.
|
||||||
|
* This class should not use system calls or other potentially blocking code.
|
||||||
|
*/
|
||||||
|
class cubeb_log_message
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
cubeb_log_message()
|
||||||
|
{
|
||||||
|
*storage = '\0';
|
||||||
|
}
|
||||||
|
cubeb_log_message(char const str[CUBEB_LOG_MESSAGE_MAX_SIZE])
|
||||||
|
{
|
||||||
|
size_t length = strlen(str);
|
||||||
|
/* paranoia against malformed message */
|
||||||
|
assert(length < CUBEB_LOG_MESSAGE_MAX_SIZE);
|
||||||
|
if (length > CUBEB_LOG_MESSAGE_MAX_SIZE - 1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
PodCopy(storage, str, length);
|
||||||
|
storage[length] = '\0';
|
||||||
|
}
|
||||||
|
char const * get() {
|
||||||
|
return storage;
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
char storage[CUBEB_LOG_MESSAGE_MAX_SIZE];
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Lock-free asynchronous logger, made so that logging from a
|
||||||
|
* real-time audio callback does not block the audio thread. */
|
||||||
|
class cubeb_async_logger
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
/* This is thread-safe since C++11 */
|
||||||
|
static cubeb_async_logger & get() {
|
||||||
|
static cubeb_async_logger instance;
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
void push(char const str[CUBEB_LOG_MESSAGE_MAX_SIZE])
|
||||||
|
{
|
||||||
|
cubeb_log_message msg(str);
|
||||||
|
msg_queue.enqueue(msg);
|
||||||
|
}
|
||||||
|
void run()
|
||||||
|
{
|
||||||
|
std::thread([this]() {
|
||||||
|
while (true) {
|
||||||
|
cubeb_log_message msg;
|
||||||
|
while (msg_queue.dequeue(&msg, 1)) {
|
||||||
|
LOGV("%s", msg.get());
|
||||||
|
}
|
||||||
|
#ifdef _WIN32
|
||||||
|
Sleep(CUBEB_LOG_BATCH_PRINT_INTERVAL_MS);
|
||||||
|
#else
|
||||||
|
timespec sleep_duration = sleep_for;
|
||||||
|
timespec remainder;
|
||||||
|
do {
|
||||||
|
if (nanosleep(&sleep_duration, &remainder) == 0 ||
|
||||||
|
errno != EINTR) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
sleep_duration = remainder;
|
||||||
|
} while (remainder.tv_sec || remainder.tv_nsec);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}).detach();
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
#ifndef _WIN32
|
||||||
|
const struct timespec sleep_for = {
|
||||||
|
CUBEB_LOG_BATCH_PRINT_INTERVAL_MS/1000,
|
||||||
|
(CUBEB_LOG_BATCH_PRINT_INTERVAL_MS%1000)*1000*1000
|
||||||
|
};
|
||||||
|
#endif
|
||||||
|
cubeb_async_logger()
|
||||||
|
: msg_queue(CUBEB_LOG_MESSAGE_QUEUE_DEPTH)
|
||||||
|
{
|
||||||
|
run();
|
||||||
|
}
|
||||||
|
/** This is quite a big data structure, but is only instantiated if the
|
||||||
|
* asynchronous logger is used.*/
|
||||||
|
lock_free_queue<cubeb_log_message> msg_queue;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
void cubeb_async_log(char const * fmt, ...)
|
||||||
|
{
|
||||||
|
if (!g_cubeb_log_callback) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// This is going to copy a 256 bytes array around, which is fine.
|
||||||
|
// We don't want to allocate memory here, because this is made to
|
||||||
|
// be called from a real-time callback.
|
||||||
|
va_list args;
|
||||||
|
va_start(args, fmt);
|
||||||
|
char msg[CUBEB_LOG_MESSAGE_MAX_SIZE];
|
||||||
|
vsnprintf(msg, CUBEB_LOG_MESSAGE_MAX_SIZE, fmt, args);
|
||||||
|
cubeb_async_logger::get().push(msg);
|
||||||
|
va_end(args);
|
||||||
|
}
|
|
@ -0,0 +1,46 @@
|
||||||
|
/*
|
||||||
|
* Copyright © 2016 Mozilla Foundation
|
||||||
|
*
|
||||||
|
* This program is made available under an ISC-style license. See the
|
||||||
|
* accompanying file LICENSE for details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef CUBEB_LOG
|
||||||
|
#define CUBEB_LOG
|
||||||
|
|
||||||
|
#include "cubeb/cubeb.h"
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined(__GNUC__) || defined(__clang__)
|
||||||
|
#define PRINTF_FORMAT(fmt, args) __attribute__((format(printf, fmt, args)))
|
||||||
|
#else
|
||||||
|
#define PRINTF_FORMAT(fmt, args)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
extern cubeb_log_level g_cubeb_log_level;
|
||||||
|
extern cubeb_log_callback g_cubeb_log_callback PRINTF_FORMAT(1, 2);
|
||||||
|
void cubeb_async_log(const char * fmt, ...);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define LOGV(msg, ...) LOG_INTERNAL(CUBEB_LOG_VERBOSE, msg, ##__VA_ARGS__)
|
||||||
|
#define LOG(msg, ...) LOG_INTERNAL(CUBEB_LOG_NORMAL, msg, ##__VA_ARGS__)
|
||||||
|
|
||||||
|
#define LOG_INTERNAL(level, fmt, ...) do { \
|
||||||
|
if (g_cubeb_log_callback && level <= g_cubeb_log_level) { \
|
||||||
|
g_cubeb_log_callback("%s:%d: " fmt "\n", __FILE__, __LINE__, ##__VA_ARGS__); \
|
||||||
|
} \
|
||||||
|
} while(0)
|
||||||
|
|
||||||
|
/* Asynchronous verbose logging, to log in real-time callbacks. */
|
||||||
|
#define ALOGV(fmt, ...) \
|
||||||
|
do { \
|
||||||
|
cubeb_async_log(fmt, ##__VA_ARGS__); \
|
||||||
|
} while(0)
|
||||||
|
|
||||||
|
#endif // CUBEB_LOG
|
|
@ -0,0 +1,569 @@
|
||||||
|
/*
|
||||||
|
* Copyright © 2016 Mozilla Foundation
|
||||||
|
*
|
||||||
|
* This program is made available under an ISC-style license. See the
|
||||||
|
* accompanying file LICENSE for details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <cassert>
|
||||||
|
#include "cubeb-internal.h"
|
||||||
|
#include "cubeb_mixer.h"
|
||||||
|
|
||||||
|
// DUAL_MONO(_LFE) is same as STEREO(_LFE).
|
||||||
|
#define MASK_MONO (1 << CHANNEL_MONO)
|
||||||
|
#define MASK_MONO_LFE (MASK_MONO | (1 << CHANNEL_LFE))
|
||||||
|
#define MASK_STEREO ((1 << CHANNEL_LEFT) | (1 << CHANNEL_RIGHT))
|
||||||
|
#define MASK_STEREO_LFE (MASK_STEREO | (1 << CHANNEL_LFE))
|
||||||
|
#define MASK_3F (MASK_STEREO | (1 << CHANNEL_CENTER))
|
||||||
|
#define MASK_3F_LFE (MASK_3F | (1 << CHANNEL_LFE))
|
||||||
|
#define MASK_2F1 (MASK_STEREO | (1 << CHANNEL_RCENTER))
|
||||||
|
#define MASK_2F1_LFE (MASK_2F1 | (1 << CHANNEL_LFE))
|
||||||
|
#define MASK_3F1 (MASK_3F | (1 << CHANNEL_RCENTER))
|
||||||
|
#define MASK_3F1_LFE (MASK_3F1 | (1 << CHANNEL_LFE))
|
||||||
|
#define MASK_2F2 (MASK_STEREO | (1 << CHANNEL_LS) | (1 << CHANNEL_RS))
|
||||||
|
#define MASK_2F2_LFE (MASK_2F2 | (1 << CHANNEL_LFE))
|
||||||
|
#define MASK_3F2 (MASK_2F2 | (1 << CHANNEL_CENTER))
|
||||||
|
#define MASK_3F2_LFE (MASK_3F2 | (1 << CHANNEL_LFE))
|
||||||
|
#define MASK_3F3R_LFE (MASK_3F2_LFE | (1 << CHANNEL_RCENTER))
|
||||||
|
#define MASK_3F4_LFE (MASK_3F2_LFE | (1 << CHANNEL_RLS) | (1 << CHANNEL_RRS))
|
||||||
|
|
||||||
|
cubeb_channel_layout cubeb_channel_map_to_layout(cubeb_channel_map const * channel_map)
|
||||||
|
{
|
||||||
|
uint32_t channel_mask = 0;
|
||||||
|
for (uint8_t i = 0 ; i < channel_map->channels ; ++i) {
|
||||||
|
if (channel_map->map[i] == CHANNEL_INVALID ||
|
||||||
|
channel_map->map[i] == CHANNEL_UNMAPPED) {
|
||||||
|
return CUBEB_LAYOUT_UNDEFINED;
|
||||||
|
}
|
||||||
|
channel_mask |= 1 << channel_map->map[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
switch(channel_mask) {
|
||||||
|
case MASK_MONO: return CUBEB_LAYOUT_MONO;
|
||||||
|
case MASK_MONO_LFE: return CUBEB_LAYOUT_MONO_LFE;
|
||||||
|
case MASK_STEREO: return CUBEB_LAYOUT_STEREO;
|
||||||
|
case MASK_STEREO_LFE: return CUBEB_LAYOUT_STEREO_LFE;
|
||||||
|
case MASK_3F: return CUBEB_LAYOUT_3F;
|
||||||
|
case MASK_3F_LFE: return CUBEB_LAYOUT_3F_LFE;
|
||||||
|
case MASK_2F1: return CUBEB_LAYOUT_2F1;
|
||||||
|
case MASK_2F1_LFE: return CUBEB_LAYOUT_2F1_LFE;
|
||||||
|
case MASK_3F1: return CUBEB_LAYOUT_3F1;
|
||||||
|
case MASK_3F1_LFE: return CUBEB_LAYOUT_3F1_LFE;
|
||||||
|
case MASK_2F2: return CUBEB_LAYOUT_2F2;
|
||||||
|
case MASK_2F2_LFE: return CUBEB_LAYOUT_2F2_LFE;
|
||||||
|
case MASK_3F2: return CUBEB_LAYOUT_3F2;
|
||||||
|
case MASK_3F2_LFE: return CUBEB_LAYOUT_3F2_LFE;
|
||||||
|
case MASK_3F3R_LFE: return CUBEB_LAYOUT_3F3R_LFE;
|
||||||
|
case MASK_3F4_LFE: return CUBEB_LAYOUT_3F4_LFE;
|
||||||
|
default: return CUBEB_LAYOUT_UNDEFINED;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cubeb_layout_map const CUBEB_CHANNEL_LAYOUT_MAPS[CUBEB_LAYOUT_MAX] = {
|
||||||
|
{ "undefined", 0, CUBEB_LAYOUT_UNDEFINED },
|
||||||
|
{ "dual mono", 2, CUBEB_LAYOUT_DUAL_MONO },
|
||||||
|
{ "dual mono lfe", 3, CUBEB_LAYOUT_DUAL_MONO_LFE },
|
||||||
|
{ "mono", 1, CUBEB_LAYOUT_MONO },
|
||||||
|
{ "mono lfe", 2, CUBEB_LAYOUT_MONO_LFE },
|
||||||
|
{ "stereo", 2, CUBEB_LAYOUT_STEREO },
|
||||||
|
{ "stereo lfe", 3, CUBEB_LAYOUT_STEREO_LFE },
|
||||||
|
{ "3f", 3, CUBEB_LAYOUT_3F },
|
||||||
|
{ "3f lfe", 4, CUBEB_LAYOUT_3F_LFE },
|
||||||
|
{ "2f1", 3, CUBEB_LAYOUT_2F1 },
|
||||||
|
{ "2f1 lfe", 4, CUBEB_LAYOUT_2F1_LFE },
|
||||||
|
{ "3f1", 4, CUBEB_LAYOUT_3F1 },
|
||||||
|
{ "3f1 lfe", 5, CUBEB_LAYOUT_3F1_LFE },
|
||||||
|
{ "2f2", 4, CUBEB_LAYOUT_2F2 },
|
||||||
|
{ "2f2 lfe", 5, CUBEB_LAYOUT_2F2_LFE },
|
||||||
|
{ "3f2", 5, CUBEB_LAYOUT_3F2 },
|
||||||
|
{ "3f2 lfe", 6, CUBEB_LAYOUT_3F2_LFE },
|
||||||
|
{ "3f3r lfe", 7, CUBEB_LAYOUT_3F3R_LFE },
|
||||||
|
{ "3f4 lfe", 8, CUBEB_LAYOUT_3F4_LFE }
|
||||||
|
};
|
||||||
|
|
||||||
|
static int const CHANNEL_ORDER_TO_INDEX[CUBEB_LAYOUT_MAX][CHANNEL_MAX] = {
|
||||||
|
// M | L | R | C | LS | RS | RLS | RC | RRS | LFE
|
||||||
|
{ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, // UNDEFINED
|
||||||
|
{ -1, 0, 1, -1, -1, -1, -1, -1, -1, -1 }, // DUAL_MONO
|
||||||
|
{ -1, 0, 1, -1, -1, -1, -1, -1, -1, 2 }, // DUAL_MONO_LFE
|
||||||
|
{ 0, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, // MONO
|
||||||
|
{ 0, -1, -1, -1, -1, -1, -1, -1, -1, 1 }, // MONO_LFE
|
||||||
|
{ -1, 0, 1, -1, -1, -1, -1, -1, -1, -1 }, // STEREO
|
||||||
|
{ -1, 0, 1, -1, -1, -1, -1, -1, -1, 2 }, // STEREO_LFE
|
||||||
|
{ -1, 0, 1, 2, -1, -1, -1, -1, -1, -1 }, // 3F
|
||||||
|
{ -1, 0, 1, 2, -1, -1, -1, -1, -1, 3 }, // 3F_LFE
|
||||||
|
{ -1, 0, 1, -1, -1, -1, -1, 2, -1, -1 }, // 2F1
|
||||||
|
{ -1, 0, 1, -1, -1, -1, -1, 3, -1, 2 }, // 2F1_LFE
|
||||||
|
{ -1, 0, 1, 2, -1, -1, -1, 3, -1, -1 }, // 3F1
|
||||||
|
{ -1, 0, 1, 2, -1, -1, -1, 4, -1, 3 }, // 3F1_LFE
|
||||||
|
{ -1, 0, 1, -1, 2, 3, -1, -1, -1, -1 }, // 2F2
|
||||||
|
{ -1, 0, 1, -1, 3, 4, -1, -1, -1, 2 }, // 2F2_LFE
|
||||||
|
{ -1, 0, 1, 2, 3, 4, -1, -1, -1, -1 }, // 3F2
|
||||||
|
{ -1, 0, 1, 2, 4, 5, -1, -1, -1, 3 }, // 3F2_LFE
|
||||||
|
{ -1, 0, 1, 2, 5, 6, -1, 4, -1, 3 }, // 3F3R_LFE
|
||||||
|
{ -1, 0, 1, 2, 6, 7, 4, -1, 5, 3 }, // 3F4_LFE
|
||||||
|
};
|
||||||
|
|
||||||
|
// The downmix matrix from TABLE 2 in the ITU-R BS.775-3[1] defines a way to
|
||||||
|
// convert 3F2 input data to 1F, 2F, 3F, 2F1, 3F1, 2F2 output data. We extend it
|
||||||
|
// to convert 3F2-LFE input data to 1F, 2F, 3F, 2F1, 3F1, 2F2 and their LFEs
|
||||||
|
// output data.
|
||||||
|
// [1] https://www.itu.int/dms_pubrec/itu-r/rec/bs/R-REC-BS.775-3-201208-I!!PDF-E.pdf
|
||||||
|
|
||||||
|
// Number of converted layouts: 1F, 2F, 3F, 2F1, 3F1, 2F2 and their LFEs.
|
||||||
|
unsigned int const SUPPORTED_LAYOUT_NUM = 12;
|
||||||
|
// Number of input channel for downmix conversion.
|
||||||
|
unsigned int const INPUT_CHANNEL_NUM = 6; // 3F2-LFE
|
||||||
|
// Max number of possible output channels.
|
||||||
|
unsigned int const MAX_OUTPUT_CHANNEL_NUM = 5; // 2F2-LFE or 3F1-LFE
|
||||||
|
float const INV_SQRT_2 = 0.707106f; // 1/sqrt(2)
|
||||||
|
// Each array contains coefficients that will be multiplied with
|
||||||
|
// { L, R, C, LFE, LS, RS } channels respectively.
|
||||||
|
static float const DOWNMIX_MATRIX_3F2_LFE[SUPPORTED_LAYOUT_NUM][MAX_OUTPUT_CHANNEL_NUM][INPUT_CHANNEL_NUM] =
|
||||||
|
{
|
||||||
|
// 1F Mono
|
||||||
|
{
|
||||||
|
{ INV_SQRT_2, INV_SQRT_2, 1, 0, 0.5, 0.5 }, // M
|
||||||
|
},
|
||||||
|
// 1F Mono-LFE
|
||||||
|
{
|
||||||
|
{ INV_SQRT_2, INV_SQRT_2, 1, 0, 0.5, 0.5 }, // M
|
||||||
|
{ 0, 0, 0, 1, 0, 0 } // LFE
|
||||||
|
},
|
||||||
|
// 2F Stereo
|
||||||
|
{
|
||||||
|
{ 1, 0, INV_SQRT_2, 0, INV_SQRT_2, 0 }, // L
|
||||||
|
{ 0, 1, INV_SQRT_2, 0, 0, INV_SQRT_2 } // R
|
||||||
|
},
|
||||||
|
// 2F Stereo-LFE
|
||||||
|
{
|
||||||
|
{ 1, 0, INV_SQRT_2, 0, INV_SQRT_2, 0 }, // L
|
||||||
|
{ 0, 1, INV_SQRT_2, 0, 0, INV_SQRT_2 }, // R
|
||||||
|
{ 0, 0, 0, 1, 0, 0 } // LFE
|
||||||
|
},
|
||||||
|
// 3F
|
||||||
|
{
|
||||||
|
{ 1, 0, 0, 0, INV_SQRT_2, 0 }, // L
|
||||||
|
{ 0, 1, 0, 0, 0, INV_SQRT_2 }, // R
|
||||||
|
{ 0, 0, 1, 0, 0, 0 } // C
|
||||||
|
},
|
||||||
|
// 3F-LFE
|
||||||
|
{
|
||||||
|
{ 1, 0, 0, 0, INV_SQRT_2, 0 }, // L
|
||||||
|
{ 0, 1, 0, 0, 0, INV_SQRT_2 }, // R
|
||||||
|
{ 0, 0, 1, 0, 0, 0 }, // C
|
||||||
|
{ 0, 0, 0, 1, 0, 0 } // LFE
|
||||||
|
},
|
||||||
|
// 2F1
|
||||||
|
{
|
||||||
|
{ 1, 0, INV_SQRT_2, 0, 0, 0 }, // L
|
||||||
|
{ 0, 1, INV_SQRT_2, 0, 0, 0 }, // R
|
||||||
|
{ 0, 0, 0, 0, INV_SQRT_2, INV_SQRT_2 } // S
|
||||||
|
},
|
||||||
|
// 2F1-LFE
|
||||||
|
{
|
||||||
|
{ 1, 0, INV_SQRT_2, 0, 0, 0 }, // L
|
||||||
|
{ 0, 1, INV_SQRT_2, 0, 0, 0 }, // R
|
||||||
|
{ 0, 0, 0, 1, 0, 0 }, // LFE
|
||||||
|
{ 0, 0, 0, 0, INV_SQRT_2, INV_SQRT_2 } // S
|
||||||
|
},
|
||||||
|
// 3F1
|
||||||
|
{
|
||||||
|
{ 1, 0, 0, 0, 0, 0 }, // L
|
||||||
|
{ 0, 1, 0, 0, 0, 0 }, // R
|
||||||
|
{ 0, 0, 1, 0, 0, 0 }, // C
|
||||||
|
{ 0, 0, 0, 0, INV_SQRT_2, INV_SQRT_2 } // S
|
||||||
|
},
|
||||||
|
// 3F1-LFE
|
||||||
|
{
|
||||||
|
{ 1, 0, 0, 0, 0, 0 }, // L
|
||||||
|
{ 0, 1, 0, 0, 0, 0 }, // R
|
||||||
|
{ 0, 0, 1, 0, 0, 0 }, // C
|
||||||
|
{ 0, 0, 0, 1, 0, 0 }, // LFE
|
||||||
|
{ 0, 0, 0, 0, INV_SQRT_2, INV_SQRT_2 } // S
|
||||||
|
},
|
||||||
|
// 2F2
|
||||||
|
{
|
||||||
|
{ 1, 0, INV_SQRT_2, 0, 0, 0 }, // L
|
||||||
|
{ 0, 1, INV_SQRT_2, 0, 0, 0 }, // R
|
||||||
|
{ 0, 0, 0, 0, 1, 0 }, // LS
|
||||||
|
{ 0, 0, 0, 0, 0, 1 } // RS
|
||||||
|
},
|
||||||
|
// 2F2-LFE
|
||||||
|
{
|
||||||
|
{ 1, 0, INV_SQRT_2, 0, 0, 0 }, // L
|
||||||
|
{ 0, 1, INV_SQRT_2, 0, 0, 0 }, // R
|
||||||
|
{ 0, 0, 0, 1, 0, 0 }, // LFE
|
||||||
|
{ 0, 0, 0, 0, 1, 0 }, // LS
|
||||||
|
{ 0, 0, 0, 0, 0, 1 } // RS
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Convert audio data from 3F2(-LFE) to 1F, 2F, 3F, 2F1, 3F1, 2F2 and their LFEs.
|
||||||
|
//
|
||||||
|
// ITU-R BS.775-3[1] provides spec for downmixing 3F2 data to 1F, 2F, 3F, 2F1,
|
||||||
|
// 3F1, 2F2 data. We simply add LFE to its defined matrix. If both the input
|
||||||
|
// and output have LFE channel, then we pass it's data. If only input or output
|
||||||
|
// has LFE, then we either drop it or append 0 to the LFE channel.
|
||||||
|
//
|
||||||
|
// Fig. 1:
|
||||||
|
// |<-------------- 1 -------------->|<-------------- 2 -------------->|
|
||||||
|
// +----+----+----+------+-----+-----+----+----+----+------+-----+-----+
|
||||||
|
// | L0 | R0 | C0 | LFE0 | LS0 | RS0 | L1 | R1 | C1 | LFE1 | LS1 | RS1 | ...
|
||||||
|
// +----+----+----+------+-----+-----+----+----+----+------+-----+-----+
|
||||||
|
//
|
||||||
|
// Fig. 2:
|
||||||
|
// |<-- 1 -->|<-- 2 -->|
|
||||||
|
// +----+----+----+----+
|
||||||
|
// | L0 | R0 | L1 | R1 | ...
|
||||||
|
// +----+----+----+----+
|
||||||
|
//
|
||||||
|
// The figures above shows an example for downmixing from 3F2-LFE(Fig. 1) to
|
||||||
|
// to stereo(Fig. 2), where L0 = L0 + 0.707 * (C0 + LS0),
|
||||||
|
// R0 = R0 + 0.707 * (C0 + RS0), L1 = L1 + 0.707 * (C1 + LS1),
|
||||||
|
// R1 = R1 + 0.707 * (C1 + RS1), ...
|
||||||
|
//
|
||||||
|
// Nevertheless, the downmixing method is a little bit different on OSX.
|
||||||
|
// The audio rendering mechanism on OS X will drop the extra channels beyond
|
||||||
|
// the channels that audio device can provide. The trick here is that OSX allows
|
||||||
|
// us to set the layout containing other channels that the output device can
|
||||||
|
// NOT provide. For example, setting 3F2-LFE layout to a stereo device is fine.
|
||||||
|
// Therefore, OSX expects we fill the buffer for playing sound by the defined
|
||||||
|
// layout, so there are some will-be-dropped data in the buffer:
|
||||||
|
//
|
||||||
|
// +---+---+---+-----+----+----+
|
||||||
|
// | L | R | C | LFE | LS | RS | ...
|
||||||
|
// +---+---+---+-----+----+----+
|
||||||
|
// ^ ^ ^ ^
|
||||||
|
// The data for these four channels will be dropped!
|
||||||
|
//
|
||||||
|
// To keep all the information, we need to downmix the data before it's dropped.
|
||||||
|
// The figure below shows an example for downmixing from 3F2-LFE(Fig. 1)
|
||||||
|
// to stereo(Fig. 3) on OSX, where the LO, R0, L1, R0 are same as above.
|
||||||
|
//
|
||||||
|
// Fig. 3:
|
||||||
|
// |<---------- 1 ---------->|<---------- 2 ---------->|
|
||||||
|
// +----+----+---+---+---+---+----+----+---+---+---+---+
|
||||||
|
// | L0 | R0 | x | x | x | x | L1 | R1 | x | x | x | x | ...
|
||||||
|
// +----+----+---+---+---+---+----+----+---+---+---+---+
|
||||||
|
// |<-- dummy -->| |<-- dummy -->|
|
||||||
|
template<typename T>
|
||||||
|
bool
|
||||||
|
downmix_3f2(unsigned long inframes,
|
||||||
|
T const * const in, unsigned long in_len,
|
||||||
|
T * out, unsigned long out_len,
|
||||||
|
cubeb_channel_layout in_layout, cubeb_channel_layout out_layout)
|
||||||
|
{
|
||||||
|
if ((in_layout != CUBEB_LAYOUT_3F2 && in_layout != CUBEB_LAYOUT_3F2_LFE) ||
|
||||||
|
out_layout < CUBEB_LAYOUT_MONO || out_layout > CUBEB_LAYOUT_2F2_LFE) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned int in_channels = CUBEB_CHANNEL_LAYOUT_MAPS[in_layout].channels;
|
||||||
|
unsigned int out_channels = CUBEB_CHANNEL_LAYOUT_MAPS[out_layout].channels;
|
||||||
|
|
||||||
|
// Conversion from 3F2 to 2F2-LFE or 3F1-LFE is allowed, so we use '<=' instead of '<'.
|
||||||
|
assert(out_channels <= in_channels);
|
||||||
|
|
||||||
|
auto & downmix_matrix = DOWNMIX_MATRIX_3F2_LFE[out_layout - CUBEB_LAYOUT_MONO]; // The matrix is started from mono.
|
||||||
|
unsigned long out_index = 0;
|
||||||
|
for (unsigned long i = 0 ; i < inframes * in_channels; i += in_channels) {
|
||||||
|
for (unsigned int j = 0; j < out_channels; ++j) {
|
||||||
|
T sample = 0;
|
||||||
|
for (unsigned int k = 0 ; k < INPUT_CHANNEL_NUM ; ++k) {
|
||||||
|
// 3F2-LFE has 6 channels: L, R, C, LFE, LS, RS, while 3F2 has only 5
|
||||||
|
// channels: L, R, C, LS, RS. Thus, we need to append 0 to LFE(index 3)
|
||||||
|
// to simulate a 3F2-LFE data when input layout is 3F2.
|
||||||
|
assert((in_layout == CUBEB_LAYOUT_3F2_LFE || k < 3) ? (i + k < in_len) : (k == 3) ? true : (i + k - 1 < in_len));
|
||||||
|
T data = (in_layout == CUBEB_LAYOUT_3F2_LFE) ? in[i + k] : (k == 3) ? 0 : in[i + ((k < 3) ? k : k - 1)];
|
||||||
|
sample += downmix_matrix[j][k] * data;
|
||||||
|
}
|
||||||
|
assert(out_index + j < out_len);
|
||||||
|
out[out_index + j] = sample;
|
||||||
|
}
|
||||||
|
#if defined(USE_AUDIOUNIT)
|
||||||
|
out_index += in_channels;
|
||||||
|
#else
|
||||||
|
out_index += out_channels;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Map the audio data by channel name. */
|
||||||
|
template<class T>
|
||||||
|
bool
|
||||||
|
mix_remap(long inframes,
|
||||||
|
T const * const in, unsigned long in_len,
|
||||||
|
T * out, unsigned long out_len,
|
||||||
|
cubeb_channel_layout in_layout, cubeb_channel_layout out_layout)
|
||||||
|
{
|
||||||
|
assert(in_layout != out_layout);
|
||||||
|
|
||||||
|
// We might overwrite the data before we copied them to the mapped index
|
||||||
|
// (e.g. upmixing from stereo to 2F1 or mapping [L, R] to [R, L])
|
||||||
|
if (in == out) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned int in_channels = CUBEB_CHANNEL_LAYOUT_MAPS[in_layout].channels;
|
||||||
|
unsigned int out_channels = CUBEB_CHANNEL_LAYOUT_MAPS[out_layout].channels;
|
||||||
|
|
||||||
|
uint32_t in_layout_mask = 0;
|
||||||
|
for (unsigned int i = 0 ; i < in_channels ; ++i) {
|
||||||
|
in_layout_mask |= 1 << CHANNEL_INDEX_TO_ORDER[in_layout][i];
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t out_layout_mask = 0;
|
||||||
|
for (unsigned int i = 0 ; i < out_channels ; ++i) {
|
||||||
|
out_layout_mask |= 1 << CHANNEL_INDEX_TO_ORDER[out_layout][i];
|
||||||
|
}
|
||||||
|
|
||||||
|
// If there is no matched channel, then do nothing.
|
||||||
|
if (!(out_layout_mask & in_layout_mask)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (unsigned long i = 0, out_index = 0; i < inframes * in_channels; i += in_channels, out_index += out_channels) {
|
||||||
|
for (unsigned int j = 0; j < out_channels; ++j) {
|
||||||
|
cubeb_channel channel = CHANNEL_INDEX_TO_ORDER[out_layout][j];
|
||||||
|
uint32_t channel_mask = 1 << channel;
|
||||||
|
int channel_index = CHANNEL_ORDER_TO_INDEX[in_layout][channel];
|
||||||
|
assert(out_index + j < out_len);
|
||||||
|
if (in_layout_mask & channel_mask) {
|
||||||
|
assert(i + channel_index < in_len);
|
||||||
|
assert(channel_index != -1);
|
||||||
|
out[out_index + j] = in[i + channel_index];
|
||||||
|
} else {
|
||||||
|
assert(channel_index == -1);
|
||||||
|
out[out_index + j] = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Drop the extra channels beyond the provided output channels. */
|
||||||
|
template<typename T>
|
||||||
|
void
|
||||||
|
downmix_fallback(long inframes,
|
||||||
|
T const * const in, unsigned long in_len,
|
||||||
|
T * out, unsigned long out_len,
|
||||||
|
unsigned int in_channels, unsigned int out_channels)
|
||||||
|
{
|
||||||
|
assert(in_channels >= out_channels);
|
||||||
|
|
||||||
|
if (in_channels == out_channels && in == out) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (unsigned long i = 0, out_index = 0; i < inframes * in_channels; i += in_channels, out_index += out_channels) {
|
||||||
|
for (unsigned int j = 0; j < out_channels; ++j) {
|
||||||
|
assert(i + j < in_len && out_index + j < out_len);
|
||||||
|
out[out_index + j] = in[i + j];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
void
|
||||||
|
cubeb_downmix(long inframes,
|
||||||
|
T const * const in, unsigned long in_len,
|
||||||
|
T * out, unsigned long out_len,
|
||||||
|
cubeb_stream_params const * stream_params,
|
||||||
|
cubeb_stream_params const * mixer_params)
|
||||||
|
{
|
||||||
|
assert(in && out);
|
||||||
|
assert(inframes);
|
||||||
|
assert(stream_params->channels >= mixer_params->channels &&
|
||||||
|
mixer_params->channels > 0);
|
||||||
|
assert(stream_params->layout != CUBEB_LAYOUT_UNDEFINED);
|
||||||
|
|
||||||
|
unsigned int in_channels = stream_params->channels;
|
||||||
|
cubeb_channel_layout in_layout = stream_params->layout;
|
||||||
|
|
||||||
|
unsigned int out_channels = mixer_params->channels;
|
||||||
|
cubeb_channel_layout out_layout = mixer_params->layout;
|
||||||
|
|
||||||
|
// If the channel number is different from the layout's setting,
|
||||||
|
// then we use fallback downmix mechanism.
|
||||||
|
if (out_channels == CUBEB_CHANNEL_LAYOUT_MAPS[out_layout].channels &&
|
||||||
|
in_channels == CUBEB_CHANNEL_LAYOUT_MAPS[in_layout].channels) {
|
||||||
|
if (downmix_3f2(inframes, in, in_len, out, out_len, in_layout, out_layout)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if defined(USE_AUDIOUNIT)
|
||||||
|
// We only support downmix for audio 5.1 on OS X currently.
|
||||||
|
return;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (mix_remap(inframes, in, in_len, out, out_len, in_layout, out_layout)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
downmix_fallback(inframes, in, in_len, out, out_len, in_channels, out_channels);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Upmix function, copies a mono channel into L and R. */
|
||||||
|
template<typename T>
|
||||||
|
void
|
||||||
|
mono_to_stereo(long insamples, T const * in, unsigned long in_len,
|
||||||
|
T * out, unsigned long out_len, unsigned int out_channels)
|
||||||
|
{
|
||||||
|
for (long i = 0, j = 0; i < insamples; ++i, j += out_channels) {
|
||||||
|
assert(i < in_len && j + 1 < out_len);
|
||||||
|
out[j] = out[j + 1] = in[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
void
|
||||||
|
cubeb_upmix(long inframes,
|
||||||
|
T const * const in, unsigned long in_len,
|
||||||
|
T * out, unsigned long out_len,
|
||||||
|
cubeb_stream_params const * stream_params,
|
||||||
|
cubeb_stream_params const * mixer_params)
|
||||||
|
{
|
||||||
|
assert(in && out);
|
||||||
|
assert(inframes);
|
||||||
|
assert(mixer_params->channels >= stream_params->channels &&
|
||||||
|
stream_params->channels > 0);
|
||||||
|
|
||||||
|
unsigned int in_channels = stream_params->channels;
|
||||||
|
unsigned int out_channels = mixer_params->channels;
|
||||||
|
|
||||||
|
/* Either way, if we have 2 or more channels, the first two are L and R. */
|
||||||
|
/* If we are playing a mono stream over stereo speakers, copy the data over. */
|
||||||
|
if (in_channels == 1 && out_channels >= 2) {
|
||||||
|
mono_to_stereo(inframes, in, in_len, out, out_len, out_channels);
|
||||||
|
} else {
|
||||||
|
/* Copy through. */
|
||||||
|
for (unsigned int i = 0, o = 0; i < inframes * in_channels;
|
||||||
|
i += in_channels, o += out_channels) {
|
||||||
|
for (unsigned int j = 0; j < in_channels; ++j) {
|
||||||
|
assert(i + j < in_len && o + j < out_len);
|
||||||
|
out[o + j] = in[i + j];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Check if more channels. */
|
||||||
|
if (out_channels <= 2) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Put silence in remaining channels. */
|
||||||
|
for (long i = 0, o = 0; i < inframes; ++i, o += out_channels) {
|
||||||
|
for (unsigned int j = 2; j < out_channels; ++j) {
|
||||||
|
assert(o + j < out_len);
|
||||||
|
out[o + j] = 0.0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
cubeb_should_upmix(cubeb_stream_params const * stream, cubeb_stream_params const * mixer)
|
||||||
|
{
|
||||||
|
return mixer->channels > stream->channels;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
cubeb_should_downmix(cubeb_stream_params const * stream, cubeb_stream_params const * mixer)
|
||||||
|
{
|
||||||
|
if (mixer->channels > stream->channels || mixer->layout == stream->layout) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return mixer->channels < stream->channels ||
|
||||||
|
// When mixer.channels == stream.channels
|
||||||
|
mixer->layout == CUBEB_LAYOUT_UNDEFINED || // fallback downmix
|
||||||
|
(stream->layout == CUBEB_LAYOUT_3F2 && // 3f2 downmix
|
||||||
|
(mixer->layout == CUBEB_LAYOUT_2F2_LFE ||
|
||||||
|
mixer->layout == CUBEB_LAYOUT_3F1_LFE));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
cubeb_should_mix(cubeb_stream_params const * stream, cubeb_stream_params const * mixer)
|
||||||
|
{
|
||||||
|
return cubeb_should_upmix(stream, mixer) || cubeb_should_downmix(stream, mixer);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct cubeb_mixer {
|
||||||
|
virtual void mix(long frames,
|
||||||
|
void * input_buffer, unsigned long input_buffer_length,
|
||||||
|
void * output_buffer, unsigned long output_buffer_length,
|
||||||
|
cubeb_stream_params const * stream_params,
|
||||||
|
cubeb_stream_params const * mixer_params) = 0;
|
||||||
|
virtual ~cubeb_mixer() {};
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
struct cubeb_mixer_impl : public cubeb_mixer {
|
||||||
|
explicit cubeb_mixer_impl(unsigned int d)
|
||||||
|
: direction(d)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void mix(long frames,
|
||||||
|
void * input_buffer, unsigned long input_buffer_length,
|
||||||
|
void * output_buffer, unsigned long output_buffer_length,
|
||||||
|
cubeb_stream_params const * stream_params,
|
||||||
|
cubeb_stream_params const * mixer_params)
|
||||||
|
{
|
||||||
|
if (frames <= 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
T * in = static_cast<T*>(input_buffer);
|
||||||
|
T * out = static_cast<T*>(output_buffer);
|
||||||
|
|
||||||
|
if ((direction & CUBEB_MIXER_DIRECTION_DOWNMIX) &&
|
||||||
|
cubeb_should_downmix(stream_params, mixer_params)) {
|
||||||
|
cubeb_downmix(frames, in, input_buffer_length, out, output_buffer_length, stream_params, mixer_params);
|
||||||
|
} else if ((direction & CUBEB_MIXER_DIRECTION_UPMIX) &&
|
||||||
|
cubeb_should_upmix(stream_params, mixer_params)) {
|
||||||
|
cubeb_upmix(frames, in, input_buffer_length, out, output_buffer_length, stream_params, mixer_params);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
~cubeb_mixer_impl() {};
|
||||||
|
|
||||||
|
unsigned char const direction;
|
||||||
|
};
|
||||||
|
|
||||||
|
cubeb_mixer * cubeb_mixer_create(cubeb_sample_format format,
|
||||||
|
unsigned char direction)
|
||||||
|
{
|
||||||
|
assert(direction & CUBEB_MIXER_DIRECTION_DOWNMIX ||
|
||||||
|
direction & CUBEB_MIXER_DIRECTION_UPMIX);
|
||||||
|
switch(format) {
|
||||||
|
case CUBEB_SAMPLE_S16NE:
|
||||||
|
return new cubeb_mixer_impl<short>(direction);
|
||||||
|
case CUBEB_SAMPLE_FLOAT32NE:
|
||||||
|
return new cubeb_mixer_impl<float>(direction);
|
||||||
|
default:
|
||||||
|
assert(false);
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void cubeb_mixer_destroy(cubeb_mixer * mixer)
|
||||||
|
{
|
||||||
|
delete mixer;
|
||||||
|
}
|
||||||
|
|
||||||
|
void cubeb_mixer_mix(cubeb_mixer * mixer, long frames,
|
||||||
|
void * input_buffer, unsigned long input_buffer_length,
|
||||||
|
void * output_buffer, unsigned long outputput_buffer_length,
|
||||||
|
cubeb_stream_params const * stream_params,
|
||||||
|
cubeb_stream_params const * mixer_params)
|
||||||
|
{
|
||||||
|
assert(mixer);
|
||||||
|
mixer->mix(frames, input_buffer, input_buffer_length, output_buffer, outputput_buffer_length,
|
||||||
|
stream_params, mixer_params);
|
||||||
|
}
|
|
@ -0,0 +1,90 @@
|
||||||
|
/*
|
||||||
|
* Copyright © 2016 Mozilla Foundation
|
||||||
|
*
|
||||||
|
* This program is made available under an ISC-style license. See the
|
||||||
|
* accompanying file LICENSE for details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef CUBEB_MIXER
|
||||||
|
#define CUBEB_MIXER
|
||||||
|
|
||||||
|
#include "cubeb/cubeb.h" // for cubeb_channel_layout ,CUBEB_CHANNEL_LAYOUT_MAPS and cubeb_stream_params.
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
#if defined(__cplusplus)
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
CHANNEL_INVALID = -1,
|
||||||
|
CHANNEL_MONO = 0,
|
||||||
|
CHANNEL_LEFT,
|
||||||
|
CHANNEL_RIGHT,
|
||||||
|
CHANNEL_CENTER,
|
||||||
|
CHANNEL_LS,
|
||||||
|
CHANNEL_RS,
|
||||||
|
CHANNEL_RLS,
|
||||||
|
CHANNEL_RCENTER,
|
||||||
|
CHANNEL_RRS,
|
||||||
|
CHANNEL_LFE,
|
||||||
|
CHANNEL_UNMAPPED,
|
||||||
|
CHANNEL_MAX = 256 // Max number of supported channels.
|
||||||
|
} cubeb_channel;
|
||||||
|
|
||||||
|
static cubeb_channel const CHANNEL_INDEX_TO_ORDER[CUBEB_LAYOUT_MAX][CHANNEL_MAX] = {
|
||||||
|
{ CHANNEL_INVALID }, // UNDEFINED
|
||||||
|
{ CHANNEL_LEFT, CHANNEL_RIGHT }, // DUAL_MONO
|
||||||
|
{ CHANNEL_LEFT, CHANNEL_RIGHT, CHANNEL_LFE }, // DUAL_MONO_LFE
|
||||||
|
{ CHANNEL_MONO }, // MONO
|
||||||
|
{ CHANNEL_MONO, CHANNEL_LFE }, // MONO_LFE
|
||||||
|
{ CHANNEL_LEFT, CHANNEL_RIGHT }, // STEREO
|
||||||
|
{ CHANNEL_LEFT, CHANNEL_RIGHT, CHANNEL_LFE }, // STEREO_LFE
|
||||||
|
{ CHANNEL_LEFT, CHANNEL_RIGHT, CHANNEL_CENTER }, // 3F
|
||||||
|
{ CHANNEL_LEFT, CHANNEL_RIGHT, CHANNEL_CENTER, CHANNEL_LFE }, // 3F_LFE
|
||||||
|
{ CHANNEL_LEFT, CHANNEL_RIGHT, CHANNEL_RCENTER }, // 2F1
|
||||||
|
{ CHANNEL_LEFT, CHANNEL_RIGHT, CHANNEL_LFE, CHANNEL_RCENTER }, // 2F1_LFE
|
||||||
|
{ CHANNEL_LEFT, CHANNEL_RIGHT, CHANNEL_CENTER, CHANNEL_RCENTER }, // 3F1
|
||||||
|
{ CHANNEL_LEFT, CHANNEL_RIGHT, CHANNEL_CENTER, CHANNEL_LFE, CHANNEL_RCENTER }, // 3F1_LFE
|
||||||
|
{ CHANNEL_LEFT, CHANNEL_RIGHT, CHANNEL_LS, CHANNEL_RS }, // 2F2
|
||||||
|
{ CHANNEL_LEFT, CHANNEL_RIGHT, CHANNEL_LFE, CHANNEL_LS, CHANNEL_RS }, // 2F2_LFE
|
||||||
|
{ CHANNEL_LEFT, CHANNEL_RIGHT, CHANNEL_CENTER, CHANNEL_LS, CHANNEL_RS }, // 3F2
|
||||||
|
{ CHANNEL_LEFT, CHANNEL_RIGHT, CHANNEL_CENTER, CHANNEL_LFE, CHANNEL_LS, CHANNEL_RS }, // 3F2_LFE
|
||||||
|
{ CHANNEL_LEFT, CHANNEL_RIGHT, CHANNEL_CENTER, CHANNEL_LFE, CHANNEL_RCENTER, CHANNEL_LS, CHANNEL_RS }, // 3F3R_LFE
|
||||||
|
{ CHANNEL_LEFT, CHANNEL_RIGHT, CHANNEL_CENTER, CHANNEL_LFE, CHANNEL_RLS, CHANNEL_RRS, CHANNEL_LS, CHANNEL_RS } // 3F4_LFE
|
||||||
|
// When more channels are present, the stream is considered unmapped to a
|
||||||
|
// particular speaker set.
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
unsigned int channels;
|
||||||
|
cubeb_channel map[CHANNEL_MAX];
|
||||||
|
} cubeb_channel_map;
|
||||||
|
|
||||||
|
cubeb_channel_layout cubeb_channel_map_to_layout(cubeb_channel_map const * channel_map);
|
||||||
|
|
||||||
|
bool cubeb_should_upmix(cubeb_stream_params const * stream, cubeb_stream_params const * mixer);
|
||||||
|
|
||||||
|
bool cubeb_should_downmix(cubeb_stream_params const * stream, cubeb_stream_params const * mixer);
|
||||||
|
|
||||||
|
bool cubeb_should_mix(cubeb_stream_params const * stream, cubeb_stream_params const * mixer);
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
CUBEB_MIXER_DIRECTION_DOWNMIX = 0x01,
|
||||||
|
CUBEB_MIXER_DIRECTION_UPMIX = 0x02,
|
||||||
|
} cubeb_mixer_direction;
|
||||||
|
|
||||||
|
typedef struct cubeb_mixer cubeb_mixer;
|
||||||
|
cubeb_mixer * cubeb_mixer_create(cubeb_sample_format format,
|
||||||
|
unsigned char direction);
|
||||||
|
void cubeb_mixer_destroy(cubeb_mixer * mixer);
|
||||||
|
void cubeb_mixer_mix(cubeb_mixer * mixer, long frames,
|
||||||
|
void * input_buffer, unsigned long input_buffer_length,
|
||||||
|
void * output_buffer, unsigned long outputput_buffer_length,
|
||||||
|
cubeb_stream_params const * stream_params,
|
||||||
|
cubeb_stream_params const * mixer_params);
|
||||||
|
|
||||||
|
#if defined(__cplusplus)
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif // CUBEB_MIXER
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,36 @@
|
||||||
|
/*
|
||||||
|
* Copyright © 2016 Mozilla Foundation
|
||||||
|
*
|
||||||
|
* This program is made available under an ISC-style license. See the
|
||||||
|
* accompanying file LICENSE for details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <cubeb/cubeb.h>
|
||||||
|
#include "cubeb_osx_run_loop.h"
|
||||||
|
#include "cubeb_log.h"
|
||||||
|
#include <AudioUnit/AudioUnit.h>
|
||||||
|
#include <CoreAudio/AudioHardware.h>
|
||||||
|
#include <CoreAudio/HostTime.h>
|
||||||
|
#include <CoreFoundation/CoreFoundation.h>
|
||||||
|
|
||||||
|
void cubeb_set_coreaudio_notification_runloop()
|
||||||
|
{
|
||||||
|
/* 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
|
||||||
|
* occur, depending on the OSX version. */
|
||||||
|
AudioObjectPropertyAddress runloop_address = {
|
||||||
|
kAudioHardwarePropertyRunLoop,
|
||||||
|
kAudioObjectPropertyScopeGlobal,
|
||||||
|
kAudioObjectPropertyElementMaster
|
||||||
|
};
|
||||||
|
|
||||||
|
CFRunLoopRef run_loop = nullptr;
|
||||||
|
|
||||||
|
OSStatus r;
|
||||||
|
r = AudioObjectSetPropertyData(kAudioObjectSystemObject,
|
||||||
|
&runloop_address,
|
||||||
|
0, NULL, sizeof(CFRunLoopRef), &run_loop);
|
||||||
|
if (r != noErr) {
|
||||||
|
LOG("Could not make global CoreAudio notifications use their own thread.");
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
/*
|
||||||
|
* Copyright © 2014 Mozilla Foundation
|
||||||
|
*
|
||||||
|
* This program is made available under an ISC-style license. See the
|
||||||
|
* accompanying file LICENSE for details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* On OSX 10.6 and after, the notification callbacks from the audio hardware are
|
||||||
|
* called on the main thread. Setting the kAudioHardwarePropertyRunLoop property
|
||||||
|
* to null tells the OSX to use a separate thread for that.
|
||||||
|
*
|
||||||
|
* This has to be called only once per process, so it is in a separate header
|
||||||
|
* for easy integration in other code bases. */
|
||||||
|
#if defined(__cplusplus)
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
void cubeb_set_coreaudio_notification_runloop();
|
||||||
|
|
||||||
|
#if defined(__cplusplus)
|
||||||
|
}
|
||||||
|
#endif
|
|
@ -0,0 +1,60 @@
|
||||||
|
/*
|
||||||
|
* Copyright © 2014 Mozilla Foundation
|
||||||
|
*
|
||||||
|
* This program is made available under an ISC-style license. See the
|
||||||
|
* accompanying file LICENSE for details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define _USE_MATH_DEFINES
|
||||||
|
#include <math.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#include "cubeb_panner.h"
|
||||||
|
|
||||||
|
#ifndef M_PI
|
||||||
|
#define M_PI 3.14159263
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/**
|
||||||
|
* We use a cos/sin law.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
template<typename T>
|
||||||
|
void cubeb_pan_stereo_buffer(T * buf, uint32_t frames, float pan)
|
||||||
|
{
|
||||||
|
if (pan == 0.0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
/* rescale in [0; 1] */
|
||||||
|
pan += 1;
|
||||||
|
pan /= 2;
|
||||||
|
float left_gain = float(cos(pan * M_PI * 0.5));
|
||||||
|
float right_gain = float(sin(pan * M_PI * 0.5));
|
||||||
|
|
||||||
|
/* In we are panning on the left, pan the right channel into the left one and
|
||||||
|
* vice-versa. */
|
||||||
|
if (pan < 0.5) {
|
||||||
|
for (uint32_t i = 0; i < frames * 2; i+=2) {
|
||||||
|
buf[i] = T(buf[i] + buf[i + 1] * left_gain);
|
||||||
|
buf[i + 1] = T(buf[i + 1] * right_gain);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (uint32_t i = 0; i < frames * 2; i+=2) {
|
||||||
|
buf[i] = T(buf[i] * left_gain);
|
||||||
|
buf[i + 1] = T(buf[i + 1] + buf[i] * right_gain);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void cubeb_pan_stereo_buffer_float(float * buf, uint32_t frames, float pan)
|
||||||
|
{
|
||||||
|
cubeb_pan_stereo_buffer(buf, frames, pan);
|
||||||
|
}
|
||||||
|
|
||||||
|
void cubeb_pan_stereo_buffer_int(short * buf, uint32_t frames, float pan)
|
||||||
|
{
|
||||||
|
cubeb_pan_stereo_buffer(buf, frames, pan);
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,28 @@
|
||||||
|
/*
|
||||||
|
* Copyright © 2014 Mozilla Foundation
|
||||||
|
*
|
||||||
|
* This program is made available under an ISC-style license. See the
|
||||||
|
* accompanying file LICENSE for details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#if !defined(CUBEB_PANNER)
|
||||||
|
#define CUBEB_PANNER
|
||||||
|
|
||||||
|
#if defined(__cplusplus)
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pan an integer or an float stereo buffer according to a cos/sin pan law
|
||||||
|
* @param buf the buffer to pan
|
||||||
|
* @param frames the number of frames in `buf`
|
||||||
|
* @param pan a float in [-1.0; 1.0]
|
||||||
|
*/
|
||||||
|
void cubeb_pan_stereo_buffer_float(float * buf, uint32_t frames, float pan);
|
||||||
|
void cubeb_pan_stereo_buffer_int(short* buf, uint32_t frames, float pan);
|
||||||
|
|
||||||
|
#if defined(__cplusplus)
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,315 @@
|
||||||
|
/*
|
||||||
|
* Copyright © 2014 Mozilla Foundation
|
||||||
|
*
|
||||||
|
* This program is made available under an ISC-style license. See the
|
||||||
|
* accompanying file LICENSE for details.
|
||||||
|
*/
|
||||||
|
#ifndef NOMINMAX
|
||||||
|
#define NOMINMAX
|
||||||
|
#endif // NOMINMAX
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cmath>
|
||||||
|
#include <cassert>
|
||||||
|
#include <cstring>
|
||||||
|
#include <cstddef>
|
||||||
|
#include <cstdio>
|
||||||
|
#include "cubeb_resampler.h"
|
||||||
|
#include "cubeb-speex-resampler.h"
|
||||||
|
#include "cubeb_resampler_internal.h"
|
||||||
|
#include "cubeb_utils.h"
|
||||||
|
|
||||||
|
int
|
||||||
|
to_speex_quality(cubeb_resampler_quality q)
|
||||||
|
{
|
||||||
|
switch(q) {
|
||||||
|
case CUBEB_RESAMPLER_QUALITY_VOIP:
|
||||||
|
return SPEEX_RESAMPLER_QUALITY_VOIP;
|
||||||
|
case CUBEB_RESAMPLER_QUALITY_DEFAULT:
|
||||||
|
return SPEEX_RESAMPLER_QUALITY_DEFAULT;
|
||||||
|
case CUBEB_RESAMPLER_QUALITY_DESKTOP:
|
||||||
|
return SPEEX_RESAMPLER_QUALITY_DESKTOP;
|
||||||
|
default:
|
||||||
|
assert(false);
|
||||||
|
return 0XFFFFFFFF;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
passthrough_resampler<T>::passthrough_resampler(cubeb_stream * s,
|
||||||
|
cubeb_data_callback cb,
|
||||||
|
void * ptr,
|
||||||
|
uint32_t input_channels)
|
||||||
|
: processor(input_channels)
|
||||||
|
, stream(s)
|
||||||
|
, data_callback(cb)
|
||||||
|
, user_ptr(ptr)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
long passthrough_resampler<T>::fill(void * input_buffer, long * input_frames_count,
|
||||||
|
void * output_buffer, long output_frames)
|
||||||
|
{
|
||||||
|
if (input_buffer) {
|
||||||
|
assert(input_frames_count);
|
||||||
|
}
|
||||||
|
assert((input_buffer && output_buffer &&
|
||||||
|
*input_frames_count + static_cast<int>(samples_to_frames(internal_input_buffer.length())) >= output_frames) ||
|
||||||
|
(output_buffer && !input_buffer && (!input_frames_count || *input_frames_count == 0)) ||
|
||||||
|
(input_buffer && !output_buffer && output_frames == 0));
|
||||||
|
|
||||||
|
if (input_buffer) {
|
||||||
|
if (!output_buffer) {
|
||||||
|
output_frames = *input_frames_count;
|
||||||
|
}
|
||||||
|
internal_input_buffer.push(static_cast<T*>(input_buffer),
|
||||||
|
frames_to_samples(*input_frames_count));
|
||||||
|
}
|
||||||
|
|
||||||
|
long rv = data_callback(stream, user_ptr, internal_input_buffer.data(),
|
||||||
|
output_buffer, output_frames);
|
||||||
|
|
||||||
|
if (input_buffer) {
|
||||||
|
internal_input_buffer.pop(nullptr, frames_to_samples(output_frames));
|
||||||
|
*input_frames_count = output_frames;
|
||||||
|
}
|
||||||
|
|
||||||
|
return rv;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T, typename InputProcessor, typename OutputProcessor>
|
||||||
|
cubeb_resampler_speex<T, InputProcessor, OutputProcessor>
|
||||||
|
::cubeb_resampler_speex(InputProcessor * input_processor,
|
||||||
|
OutputProcessor * output_processor,
|
||||||
|
cubeb_stream * s,
|
||||||
|
cubeb_data_callback cb,
|
||||||
|
void * ptr)
|
||||||
|
: input_processor(input_processor)
|
||||||
|
, output_processor(output_processor)
|
||||||
|
, stream(s)
|
||||||
|
, data_callback(cb)
|
||||||
|
, user_ptr(ptr)
|
||||||
|
{
|
||||||
|
if (input_processor && output_processor) {
|
||||||
|
// Add some delay on the processor that has the lowest delay so that the
|
||||||
|
// streams are synchronized.
|
||||||
|
uint32_t in_latency = input_processor->latency();
|
||||||
|
uint32_t out_latency = output_processor->latency();
|
||||||
|
if (in_latency > out_latency) {
|
||||||
|
uint32_t latency_diff = in_latency - out_latency;
|
||||||
|
output_processor->add_latency(latency_diff);
|
||||||
|
} else if (in_latency < out_latency) {
|
||||||
|
uint32_t latency_diff = out_latency - in_latency;
|
||||||
|
input_processor->add_latency(latency_diff);
|
||||||
|
}
|
||||||
|
fill_internal = &cubeb_resampler_speex::fill_internal_duplex;
|
||||||
|
} else if (input_processor) {
|
||||||
|
fill_internal = &cubeb_resampler_speex::fill_internal_input;
|
||||||
|
} else if (output_processor) {
|
||||||
|
fill_internal = &cubeb_resampler_speex::fill_internal_output;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T, typename InputProcessor, typename OutputProcessor>
|
||||||
|
cubeb_resampler_speex<T, InputProcessor, 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>
|
||||||
|
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) &&
|
||||||
|
output_buffer && output_frames_needed);
|
||||||
|
|
||||||
|
long got = 0;
|
||||||
|
T * out_unprocessed = nullptr;
|
||||||
|
long output_frames_before_processing = 0;
|
||||||
|
|
||||||
|
/* fill directly the input buffer of the output processor to save a copy */
|
||||||
|
output_frames_before_processing =
|
||||||
|
output_processor->input_needed_for_output(output_frames_needed);
|
||||||
|
|
||||||
|
out_unprocessed =
|
||||||
|
output_processor->input_buffer(output_frames_before_processing);
|
||||||
|
|
||||||
|
got = data_callback(stream, user_ptr,
|
||||||
|
nullptr, out_unprocessed,
|
||||||
|
output_frames_before_processing);
|
||||||
|
|
||||||
|
if (got < 0) {
|
||||||
|
return got;
|
||||||
|
}
|
||||||
|
|
||||||
|
output_processor->written(got);
|
||||||
|
|
||||||
|
/* Process the output. If not enough frames have been returned from the
|
||||||
|
* callback, drain the processors. */
|
||||||
|
return output_processor->output(output_buffer, output_frames_needed);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T, typename InputProcessor, typename OutputProcessor>
|
||||||
|
long
|
||||||
|
cubeb_resampler_speex<T, InputProcessor, OutputProcessor>
|
||||||
|
::fill_internal_input(T * input_buffer, long * input_frames_count,
|
||||||
|
T * output_buffer, long /*output_frames_needed*/)
|
||||||
|
{
|
||||||
|
assert(input_buffer && input_frames_count && *input_frames_count &&
|
||||||
|
!output_buffer);
|
||||||
|
|
||||||
|
/* The input data, after eventual resampling. This is passed to the callback. */
|
||||||
|
T * resampled_input = nullptr;
|
||||||
|
uint32_t resampled_frame_count = input_processor->output_for_input(*input_frames_count);
|
||||||
|
|
||||||
|
/* process the input, and present exactly `output_frames_needed` in the
|
||||||
|
* callback. */
|
||||||
|
input_processor->input(input_buffer, *input_frames_count);
|
||||||
|
resampled_input = input_processor->output(resampled_frame_count, (size_t*)input_frames_count);
|
||||||
|
|
||||||
|
long got = data_callback(stream, user_ptr,
|
||||||
|
resampled_input, nullptr, resampled_frame_count);
|
||||||
|
|
||||||
|
/* Return the number of initial input frames or part of it.
|
||||||
|
* Since output_frames_needed == 0 in input scenario, the only
|
||||||
|
* available number outside resampler is the initial number of frames. */
|
||||||
|
return (*input_frames_count) * (got / resampled_frame_count);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T, typename InputProcessor, typename OutputProcessor>
|
||||||
|
long
|
||||||
|
cubeb_resampler_speex<T, InputProcessor, OutputProcessor>
|
||||||
|
::fill_internal_duplex(T * in_buffer, long * input_frames_count,
|
||||||
|
T * out_buffer, long output_frames_needed)
|
||||||
|
{
|
||||||
|
/* The input data, after eventual resampling. This is passed to the callback. */
|
||||||
|
T * resampled_input = nullptr;
|
||||||
|
/* The output buffer passed down in the callback, that might be resampled. */
|
||||||
|
T * out_unprocessed = nullptr;
|
||||||
|
size_t output_frames_before_processing = 0;
|
||||||
|
/* The number of frames returned from the callback. */
|
||||||
|
long got = 0;
|
||||||
|
|
||||||
|
/* We need to determine how much frames to present to the consumer.
|
||||||
|
* - If we have a two way stream, but we're only resampling input, we resample
|
||||||
|
* the input to the number of output frames.
|
||||||
|
* - If we have a two way stream, but we're only resampling the output, we
|
||||||
|
* resize the input buffer of the output resampler to the number of input
|
||||||
|
* frames, and we resample it afterwards.
|
||||||
|
* - If we resample both ways, we resample the input to the number of frames
|
||||||
|
* we would need to pass down to the consumer (before resampling the output),
|
||||||
|
* get the output data, and resample it to the number of frames needed by the
|
||||||
|
* caller. */
|
||||||
|
|
||||||
|
output_frames_before_processing =
|
||||||
|
output_processor->input_needed_for_output(output_frames_needed);
|
||||||
|
/* fill directly the input buffer of the output processor to save a copy */
|
||||||
|
out_unprocessed =
|
||||||
|
output_processor->input_buffer(output_frames_before_processing);
|
||||||
|
|
||||||
|
if (in_buffer) {
|
||||||
|
/* process the input, and present exactly `output_frames_needed` in the
|
||||||
|
* callback. */
|
||||||
|
input_processor->input(in_buffer, *input_frames_count);
|
||||||
|
resampled_input =
|
||||||
|
input_processor->output(output_frames_before_processing, (size_t*)input_frames_count);
|
||||||
|
} else {
|
||||||
|
resampled_input = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
got = data_callback(stream, user_ptr,
|
||||||
|
resampled_input, out_unprocessed,
|
||||||
|
output_frames_before_processing);
|
||||||
|
|
||||||
|
if (got < 0) {
|
||||||
|
return got;
|
||||||
|
}
|
||||||
|
|
||||||
|
output_processor->written(got);
|
||||||
|
|
||||||
|
/* Process the output. If not enough frames have been returned from the
|
||||||
|
* callback, drain the processors. */
|
||||||
|
return output_processor->output(out_buffer, output_frames_needed);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Resampler C API */
|
||||||
|
|
||||||
|
cubeb_resampler *
|
||||||
|
cubeb_resampler_create(cubeb_stream * stream,
|
||||||
|
cubeb_stream_params * input_params,
|
||||||
|
cubeb_stream_params * output_params,
|
||||||
|
unsigned int target_rate,
|
||||||
|
cubeb_data_callback callback,
|
||||||
|
void * user_ptr,
|
||||||
|
cubeb_resampler_quality quality)
|
||||||
|
{
|
||||||
|
cubeb_sample_format format;
|
||||||
|
|
||||||
|
assert(input_params || output_params);
|
||||||
|
|
||||||
|
if (input_params) {
|
||||||
|
format = input_params->format;
|
||||||
|
} else {
|
||||||
|
format = output_params->format;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch(format) {
|
||||||
|
case CUBEB_SAMPLE_S16NE:
|
||||||
|
return cubeb_resampler_create_internal<short>(stream,
|
||||||
|
input_params,
|
||||||
|
output_params,
|
||||||
|
target_rate,
|
||||||
|
callback,
|
||||||
|
user_ptr,
|
||||||
|
quality);
|
||||||
|
case CUBEB_SAMPLE_FLOAT32NE:
|
||||||
|
return cubeb_resampler_create_internal<float>(stream,
|
||||||
|
input_params,
|
||||||
|
output_params,
|
||||||
|
target_rate,
|
||||||
|
callback,
|
||||||
|
user_ptr,
|
||||||
|
quality);
|
||||||
|
default:
|
||||||
|
assert(false);
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
long
|
||||||
|
cubeb_resampler_fill(cubeb_resampler * resampler,
|
||||||
|
void * input_buffer,
|
||||||
|
long * input_frames_count,
|
||||||
|
void * output_buffer,
|
||||||
|
long output_frames_needed)
|
||||||
|
{
|
||||||
|
return resampler->fill(input_buffer, input_frames_count,
|
||||||
|
output_buffer, output_frames_needed);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
cubeb_resampler_destroy(cubeb_resampler * resampler)
|
||||||
|
{
|
||||||
|
delete resampler;
|
||||||
|
}
|
||||||
|
|
||||||
|
long
|
||||||
|
cubeb_resampler_latency(cubeb_resampler * resampler)
|
||||||
|
{
|
||||||
|
return resampler->latency();
|
||||||
|
}
|
|
@ -0,0 +1,78 @@
|
||||||
|
/*
|
||||||
|
* Copyright © 2014 Mozilla Foundation
|
||||||
|
*
|
||||||
|
* This program is made available under an ISC-style license. See the
|
||||||
|
* accompanying file LICENSE for details.
|
||||||
|
*/
|
||||||
|
#ifndef CUBEB_RESAMPLER_H
|
||||||
|
#define CUBEB_RESAMPLER_H
|
||||||
|
|
||||||
|
#include "cubeb/cubeb.h"
|
||||||
|
|
||||||
|
#if defined(__cplusplus)
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
typedef struct cubeb_resampler cubeb_resampler;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
CUBEB_RESAMPLER_QUALITY_VOIP,
|
||||||
|
CUBEB_RESAMPLER_QUALITY_DEFAULT,
|
||||||
|
CUBEB_RESAMPLER_QUALITY_DESKTOP
|
||||||
|
} cubeb_resampler_quality;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a resampler to adapt the requested sample rate into something that
|
||||||
|
* is accepted by the audio backend.
|
||||||
|
* @param stream A cubeb_stream instance supplied to the data callback.
|
||||||
|
* @param params Used to calculate bytes per frame and buffer size for resampling.
|
||||||
|
* @param target_rate The sampling rate after resampling.
|
||||||
|
* @param callback A callback to request data for resampling.
|
||||||
|
* @param user_ptr User data supplied to the data callback.
|
||||||
|
* @param quality Quality of the resampler.
|
||||||
|
* @retval A non-null pointer if success.
|
||||||
|
*/
|
||||||
|
cubeb_resampler * cubeb_resampler_create(cubeb_stream * stream,
|
||||||
|
cubeb_stream_params * input_params,
|
||||||
|
cubeb_stream_params * output_params,
|
||||||
|
unsigned int target_rate,
|
||||||
|
cubeb_data_callback callback,
|
||||||
|
void * user_ptr,
|
||||||
|
cubeb_resampler_quality quality);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fill the buffer with frames acquired using the data callback. Resampling will
|
||||||
|
* happen if necessary.
|
||||||
|
* @param resampler A cubeb_resampler instance.
|
||||||
|
* @param input_buffer A buffer of input samples
|
||||||
|
* @param input_frame_count The size of the buffer. Returns the number of frames
|
||||||
|
* consumed.
|
||||||
|
* @param buffer The buffer to be filled.
|
||||||
|
* @param frames_needed Number of frames that should be produced.
|
||||||
|
* @retval Number of frames that are actually produced.
|
||||||
|
* @retval CUBEB_ERROR on error.
|
||||||
|
*/
|
||||||
|
long cubeb_resampler_fill(cubeb_resampler * resampler,
|
||||||
|
void * input_buffer,
|
||||||
|
long * input_frame_count,
|
||||||
|
void * output_buffer,
|
||||||
|
long output_frames_needed);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Destroy a cubeb_resampler.
|
||||||
|
* @param resampler A cubeb_resampler instance.
|
||||||
|
*/
|
||||||
|
void cubeb_resampler_destroy(cubeb_resampler * resampler);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the latency, in frames, of the resampler.
|
||||||
|
* @param resampler A cubeb resampler instance.
|
||||||
|
* @retval The latency, in frames, induced by the resampler.
|
||||||
|
*/
|
||||||
|
long cubeb_resampler_latency(cubeb_resampler * resampler);
|
||||||
|
|
||||||
|
#if defined(__cplusplus)
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif /* CUBEB_RESAMPLER_H */
|
|
@ -0,0 +1,556 @@
|
||||||
|
/*
|
||||||
|
* Copyright © 2016 Mozilla Foundation
|
||||||
|
*
|
||||||
|
* This program is made available under an ISC-style license. See the
|
||||||
|
* accompanying file LICENSE for details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#if !defined(CUBEB_RESAMPLER_INTERNAL)
|
||||||
|
#define CUBEB_RESAMPLER_INTERNAL
|
||||||
|
|
||||||
|
#include <cmath>
|
||||||
|
#include <cassert>
|
||||||
|
#include <algorithm>
|
||||||
|
#include <memory>
|
||||||
|
#ifdef CUBEB_GECKO_BUILD
|
||||||
|
#include "mozilla/UniquePtr.h"
|
||||||
|
// In libc++, symbols such as std::unique_ptr may be defined in std::__1.
|
||||||
|
// The _LIBCPP_BEGIN_NAMESPACE_STD and _LIBCPP_END_NAMESPACE_STD macros
|
||||||
|
// will expand to the correct namespace.
|
||||||
|
#ifdef _LIBCPP_BEGIN_NAMESPACE_STD
|
||||||
|
#define MOZ_BEGIN_STD_NAMESPACE _LIBCPP_BEGIN_NAMESPACE_STD
|
||||||
|
#define MOZ_END_STD_NAMESPACE _LIBCPP_END_NAMESPACE_STD
|
||||||
|
#else
|
||||||
|
#define MOZ_BEGIN_STD_NAMESPACE namespace std {
|
||||||
|
#define MOZ_END_STD_NAMESPACE }
|
||||||
|
#endif
|
||||||
|
MOZ_BEGIN_STD_NAMESPACE
|
||||||
|
using mozilla::DefaultDelete;
|
||||||
|
using mozilla::UniquePtr;
|
||||||
|
#define default_delete DefaultDelete
|
||||||
|
#define unique_ptr UniquePtr
|
||||||
|
MOZ_END_STD_NAMESPACE
|
||||||
|
#endif
|
||||||
|
#include "cubeb/cubeb.h"
|
||||||
|
#include "cubeb_utils.h"
|
||||||
|
#include "cubeb-speex-resampler.h"
|
||||||
|
#include "cubeb_resampler.h"
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
/* This header file contains the internal C++ API of the resamplers, for testing. */
|
||||||
|
|
||||||
|
int to_speex_quality(cubeb_resampler_quality q);
|
||||||
|
|
||||||
|
struct cubeb_resampler {
|
||||||
|
virtual long fill(void * input_buffer, long * input_frames_count,
|
||||||
|
void * output_buffer, long frames_needed) = 0;
|
||||||
|
virtual long latency() = 0;
|
||||||
|
virtual ~cubeb_resampler() {}
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Base class for processors. This is just used to share methods for now. */
|
||||||
|
class processor {
|
||||||
|
public:
|
||||||
|
explicit processor(uint32_t channels)
|
||||||
|
: channels(channels)
|
||||||
|
{}
|
||||||
|
protected:
|
||||||
|
size_t frames_to_samples(size_t frames)
|
||||||
|
{
|
||||||
|
return frames * channels;
|
||||||
|
}
|
||||||
|
size_t samples_to_frames(size_t samples)
|
||||||
|
{
|
||||||
|
assert(!(samples % channels));
|
||||||
|
return samples / channels;
|
||||||
|
}
|
||||||
|
/** The number of channel of the audio buffers to be resampled. */
|
||||||
|
const uint32_t channels;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
class passthrough_resampler : public cubeb_resampler
|
||||||
|
, public processor {
|
||||||
|
public:
|
||||||
|
passthrough_resampler(cubeb_stream * s,
|
||||||
|
cubeb_data_callback cb,
|
||||||
|
void * ptr,
|
||||||
|
uint32_t input_channels);
|
||||||
|
|
||||||
|
virtual long fill(void * input_buffer, long * input_frames_count,
|
||||||
|
void * output_buffer, long output_frames);
|
||||||
|
|
||||||
|
virtual long latency()
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
cubeb_stream * const stream;
|
||||||
|
const cubeb_data_callback data_callback;
|
||||||
|
void * const user_ptr;
|
||||||
|
/* This allows to buffer some input to account for the fact that we buffer
|
||||||
|
* some inputs. */
|
||||||
|
auto_array<T> internal_input_buffer;
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Bidirectional resampler, can resample an input and an output stream, or just
|
||||||
|
* an input stream or output stream. In this case a delay is inserted in the
|
||||||
|
* opposite direction to keep the streams synchronized. */
|
||||||
|
template<typename T, typename InputProcessing, typename OutputProcessing>
|
||||||
|
class cubeb_resampler_speex : public cubeb_resampler {
|
||||||
|
public:
|
||||||
|
cubeb_resampler_speex(InputProcessing * input_processor,
|
||||||
|
OutputProcessing * output_processor,
|
||||||
|
cubeb_stream * s,
|
||||||
|
cubeb_data_callback cb,
|
||||||
|
void * ptr);
|
||||||
|
|
||||||
|
virtual ~cubeb_resampler_speex();
|
||||||
|
|
||||||
|
virtual long fill(void * input_buffer, long * input_frames_count,
|
||||||
|
void * output_buffer, long output_frames_needed);
|
||||||
|
|
||||||
|
virtual long latency()
|
||||||
|
{
|
||||||
|
if (input_processor && output_processor) {
|
||||||
|
assert(input_processor->latency() == output_processor->latency());
|
||||||
|
return input_processor->latency();
|
||||||
|
} else if (input_processor) {
|
||||||
|
return input_processor->latency();
|
||||||
|
} else {
|
||||||
|
return output_processor->latency();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
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,
|
||||||
|
T * output_buffer, long output_frames_needed);
|
||||||
|
long fill_internal_input(T * input_buffer, long * input_frames_count,
|
||||||
|
T * output_buffer, long output_frames_needed);
|
||||||
|
long fill_internal_output(T * input_buffer, long * input_frames_count,
|
||||||
|
T * output_buffer, long output_frames_needed);
|
||||||
|
|
||||||
|
std::unique_ptr<InputProcessing> input_processor;
|
||||||
|
std::unique_ptr<OutputProcessing> output_processor;
|
||||||
|
processing_callback fill_internal;
|
||||||
|
cubeb_stream * const stream;
|
||||||
|
const cubeb_data_callback data_callback;
|
||||||
|
void * const user_ptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Handles one way of a (possibly) duplex resampler, working on interleaved
|
||||||
|
* 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
|
||||||
|
* input buffer, and can use the caller's output buffer, or allocate its own. */
|
||||||
|
template<typename T>
|
||||||
|
class cubeb_resampler_speex_one_way : public processor {
|
||||||
|
public:
|
||||||
|
/** The sample type of this resampler, either 16-bit integers or 32-bit
|
||||||
|
* floats. */
|
||||||
|
typedef T sample_type;
|
||||||
|
/** Construct a resampler resampling from #source_rate to #target_rate, that
|
||||||
|
* can be arbitrary, strictly positive number.
|
||||||
|
* @parameter channels The number of channels this resampler will resample.
|
||||||
|
* @parameter source_rate The sample-rate of the audio input.
|
||||||
|
* @parameter target_rate The sample-rate of the audio output.
|
||||||
|
* @parameter quality A number between 0 (fast, low quality) and 10 (slow,
|
||||||
|
* high quality). */
|
||||||
|
cubeb_resampler_speex_one_way(uint32_t channels,
|
||||||
|
uint32_t source_rate,
|
||||||
|
uint32_t target_rate,
|
||||||
|
int quality)
|
||||||
|
: processor(channels)
|
||||||
|
, resampling_ratio(static_cast<float>(source_rate) / target_rate)
|
||||||
|
, additional_latency(0)
|
||||||
|
, leftover_samples(0)
|
||||||
|
{
|
||||||
|
int r;
|
||||||
|
speex_resampler = speex_resampler_init(channels, source_rate,
|
||||||
|
target_rate, quality, &r);
|
||||||
|
assert(r == RESAMPLER_ERR_SUCCESS && "resampler allocation failure");
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Destructor, deallocate the resampler */
|
||||||
|
virtual ~cubeb_resampler_speex_one_way()
|
||||||
|
{
|
||||||
|
speex_resampler_destroy(speex_resampler);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Sometimes, it is necessary to add latency on one way of a two-way
|
||||||
|
* resampler so that the stream are synchronized. This must be called only on
|
||||||
|
* a fresh resampler, otherwise, silent samples will be inserted in the
|
||||||
|
* stream.
|
||||||
|
* @param frames the number of frames of latency to add. */
|
||||||
|
void add_latency(size_t frames)
|
||||||
|
{
|
||||||
|
additional_latency += frames;
|
||||||
|
resampling_in_buffer.push_silence(frames_to_samples(frames));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Fill the resampler with `input_frame_count` frames. */
|
||||||
|
void input(T * input_buffer, size_t input_frame_count)
|
||||||
|
{
|
||||||
|
resampling_in_buffer.push(input_buffer,
|
||||||
|
frames_to_samples(input_frame_count));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Outputs exactly `output_frame_count` into `output_buffer`.
|
||||||
|
* `output_buffer` has to be at least `output_frame_count` long. */
|
||||||
|
size_t output(T * output_buffer, size_t output_frame_count)
|
||||||
|
{
|
||||||
|
uint32_t in_len = samples_to_frames(resampling_in_buffer.length());
|
||||||
|
uint32_t out_len = output_frame_count;
|
||||||
|
|
||||||
|
speex_resample(resampling_in_buffer.data(), &in_len,
|
||||||
|
output_buffer, &out_len);
|
||||||
|
|
||||||
|
/* This shifts back any unresampled samples to the beginning of the input
|
||||||
|
buffer. */
|
||||||
|
resampling_in_buffer.pop(nullptr, frames_to_samples(in_len));
|
||||||
|
|
||||||
|
return out_len;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t output_for_input(uint32_t input_frames)
|
||||||
|
{
|
||||||
|
return (size_t)floorf((input_frames + samples_to_frames(resampling_in_buffer.length()))
|
||||||
|
/ resampling_ratio);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns a buffer containing exactly `output_frame_count` resampled frames.
|
||||||
|
* The consumer should not hold onto the pointer. */
|
||||||
|
T * output(size_t output_frame_count, size_t * input_frames_used)
|
||||||
|
{
|
||||||
|
if (resampling_out_buffer.capacity() < frames_to_samples(output_frame_count)) {
|
||||||
|
resampling_out_buffer.reserve(frames_to_samples(output_frame_count));
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t in_len = samples_to_frames(resampling_in_buffer.length());
|
||||||
|
uint32_t out_len = output_frame_count;
|
||||||
|
|
||||||
|
speex_resample(resampling_in_buffer.data(), &in_len,
|
||||||
|
resampling_out_buffer.data(), &out_len);
|
||||||
|
|
||||||
|
assert(out_len == output_frame_count);
|
||||||
|
|
||||||
|
/* This shifts back any unresampled samples to the beginning of the input
|
||||||
|
buffer. */
|
||||||
|
resampling_in_buffer.pop(nullptr, frames_to_samples(in_len));
|
||||||
|
*input_frames_used = in_len;
|
||||||
|
|
||||||
|
return resampling_out_buffer.data();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Get the latency of the resampler, in output frames. */
|
||||||
|
uint32_t latency() const
|
||||||
|
{
|
||||||
|
/* The documentation of the resampler talks about "samples" here, but it
|
||||||
|
* only consider a single channel here so it's the same number of frames. */
|
||||||
|
int latency = 0;
|
||||||
|
|
||||||
|
latency =
|
||||||
|
speex_resampler_get_output_latency(speex_resampler) + additional_latency;
|
||||||
|
|
||||||
|
assert(latency >= 0);
|
||||||
|
|
||||||
|
return latency;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns the number of frames to pass in the input of the resampler to have
|
||||||
|
* exactly `output_frame_count` resampled frames. This can return a number
|
||||||
|
* slightly bigger than what is strictly necessary, but it guaranteed that the
|
||||||
|
* number of output frames will be exactly equal. */
|
||||||
|
uint32_t input_needed_for_output(uint32_t output_frame_count)
|
||||||
|
{
|
||||||
|
int32_t unresampled_frames_left = samples_to_frames(resampling_in_buffer.length());
|
||||||
|
int32_t resampled_frames_left = samples_to_frames(resampling_out_buffer.length());
|
||||||
|
float input_frames_needed =
|
||||||
|
(output_frame_count - unresampled_frames_left) * resampling_ratio
|
||||||
|
- resampled_frames_left;
|
||||||
|
if (input_frames_needed < 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return (uint32_t)ceilf(input_frames_needed);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns a pointer to the input buffer, that contains empty space for at
|
||||||
|
* least `frame_count` elements. This is useful so that consumer can directly
|
||||||
|
* write into the input buffer of the resampler. The pointer returned is
|
||||||
|
* adjusted so that leftover data are not overwritten.
|
||||||
|
*/
|
||||||
|
T * input_buffer(size_t frame_count)
|
||||||
|
{
|
||||||
|
leftover_samples = resampling_in_buffer.length();
|
||||||
|
resampling_in_buffer.reserve(leftover_samples +
|
||||||
|
frames_to_samples(frame_count));
|
||||||
|
return resampling_in_buffer.data() + leftover_samples;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** This method works with `input_buffer`, and allows to inform the processor
|
||||||
|
how much frames have been written in the provided buffer. */
|
||||||
|
void written(size_t written_frames)
|
||||||
|
{
|
||||||
|
resampling_in_buffer.set_length(leftover_samples +
|
||||||
|
frames_to_samples(written_frames));
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
/** Wrapper for the speex resampling functions to have a typed
|
||||||
|
* interface. */
|
||||||
|
void speex_resample(float * input_buffer, uint32_t * input_frame_count,
|
||||||
|
float * output_buffer, uint32_t * output_frame_count)
|
||||||
|
{
|
||||||
|
#ifndef NDEBUG
|
||||||
|
int rv;
|
||||||
|
rv =
|
||||||
|
#endif
|
||||||
|
speex_resampler_process_interleaved_float(speex_resampler,
|
||||||
|
input_buffer,
|
||||||
|
input_frame_count,
|
||||||
|
output_buffer,
|
||||||
|
output_frame_count);
|
||||||
|
assert(rv == RESAMPLER_ERR_SUCCESS);
|
||||||
|
}
|
||||||
|
|
||||||
|
void speex_resample(short * input_buffer, uint32_t * input_frame_count,
|
||||||
|
short * output_buffer, uint32_t * output_frame_count)
|
||||||
|
{
|
||||||
|
#ifndef NDEBUG
|
||||||
|
int rv;
|
||||||
|
rv =
|
||||||
|
#endif
|
||||||
|
speex_resampler_process_interleaved_int(speex_resampler,
|
||||||
|
input_buffer,
|
||||||
|
input_frame_count,
|
||||||
|
output_buffer,
|
||||||
|
output_frame_count);
|
||||||
|
assert(rv == RESAMPLER_ERR_SUCCESS);
|
||||||
|
}
|
||||||
|
/** The state for the speex resampler used internaly. */
|
||||||
|
SpeexResamplerState * speex_resampler;
|
||||||
|
/** Source rate / target rate. */
|
||||||
|
const float resampling_ratio;
|
||||||
|
/** Storage for the input frames, to be resampled. Also contains
|
||||||
|
* any unresampled frames after resampling. */
|
||||||
|
auto_array<T> resampling_in_buffer;
|
||||||
|
/* Storage for the resampled frames, to be passed back to the caller. */
|
||||||
|
auto_array<T> resampling_out_buffer;
|
||||||
|
/** Additional latency inserted into the pipeline for synchronisation. */
|
||||||
|
uint32_t additional_latency;
|
||||||
|
/** When `input_buffer` is called, this allows tracking the number of samples
|
||||||
|
that were in the buffer. */
|
||||||
|
uint32_t leftover_samples;
|
||||||
|
};
|
||||||
|
|
||||||
|
/** This class allows delaying an audio stream by `frames` frames. */
|
||||||
|
template<typename T>
|
||||||
|
class delay_line : public processor {
|
||||||
|
public:
|
||||||
|
/** Constructor
|
||||||
|
* @parameter frames the number of frames of delay.
|
||||||
|
* @parameter channels the number of channels of this delay line. */
|
||||||
|
delay_line(uint32_t frames, uint32_t channels)
|
||||||
|
: processor(channels)
|
||||||
|
, length(frames)
|
||||||
|
, leftover_samples(0)
|
||||||
|
{
|
||||||
|
/* Fill the delay line with some silent frames to add latency. */
|
||||||
|
delay_input_buffer.push_silence(frames * channels);
|
||||||
|
}
|
||||||
|
/* Add some latency to the delay line.
|
||||||
|
* @param frames the number of frames of latency to add. */
|
||||||
|
void add_latency(size_t frames)
|
||||||
|
{
|
||||||
|
length += frames;
|
||||||
|
delay_input_buffer.push_silence(frames_to_samples(frames));
|
||||||
|
}
|
||||||
|
/** Push some frames into the delay line.
|
||||||
|
* @parameter buffer the frames to push.
|
||||||
|
* @parameter frame_count the number of frames in #buffer. */
|
||||||
|
void input(T * buffer, uint32_t frame_count)
|
||||||
|
{
|
||||||
|
delay_input_buffer.push(buffer, frames_to_samples(frame_count));
|
||||||
|
}
|
||||||
|
/** Pop some frames from the internal buffer, into a internal output buffer.
|
||||||
|
* @parameter frames_needed the number of frames to be returned.
|
||||||
|
* @return a buffer containing the delayed frames. The consumer should not
|
||||||
|
* hold onto the pointer. */
|
||||||
|
T * output(uint32_t frames_needed, size_t * input_frames_used)
|
||||||
|
{
|
||||||
|
if (delay_output_buffer.capacity() < frames_to_samples(frames_needed)) {
|
||||||
|
delay_output_buffer.reserve(frames_to_samples(frames_needed));
|
||||||
|
}
|
||||||
|
|
||||||
|
delay_output_buffer.clear();
|
||||||
|
delay_output_buffer.push(delay_input_buffer.data(),
|
||||||
|
frames_to_samples(frames_needed));
|
||||||
|
delay_input_buffer.pop(nullptr, frames_to_samples(frames_needed));
|
||||||
|
*input_frames_used = frames_needed;
|
||||||
|
|
||||||
|
return delay_output_buffer.data();
|
||||||
|
}
|
||||||
|
/** Get a pointer to the first writable location in the input buffer>
|
||||||
|
* @parameter frames_needed the number of frames the user needs to write into
|
||||||
|
* the buffer.
|
||||||
|
* @returns a pointer to a location in the input buffer where #frames_needed
|
||||||
|
* can be writen. */
|
||||||
|
T * input_buffer(uint32_t frames_needed)
|
||||||
|
{
|
||||||
|
leftover_samples = delay_input_buffer.length();
|
||||||
|
delay_input_buffer.reserve(leftover_samples + frames_to_samples(frames_needed));
|
||||||
|
return delay_input_buffer.data() + leftover_samples;
|
||||||
|
}
|
||||||
|
/** This method works with `input_buffer`, and allows to inform the processor
|
||||||
|
how much frames have been written in the provided buffer. */
|
||||||
|
void written(size_t frames_written)
|
||||||
|
{
|
||||||
|
delay_input_buffer.set_length(leftover_samples +
|
||||||
|
frames_to_samples(frames_written));
|
||||||
|
}
|
||||||
|
/** Drains the delay line, emptying the buffer.
|
||||||
|
* @parameter output_buffer the buffer in which the frames are written.
|
||||||
|
* @parameter frames_needed the maximum number of frames to write.
|
||||||
|
* @return the actual number of frames written. */
|
||||||
|
size_t output(T * output_buffer, uint32_t frames_needed)
|
||||||
|
{
|
||||||
|
uint32_t in_len = samples_to_frames(delay_input_buffer.length());
|
||||||
|
uint32_t out_len = frames_needed;
|
||||||
|
|
||||||
|
uint32_t to_pop = std::min(in_len, out_len);
|
||||||
|
|
||||||
|
delay_input_buffer.pop(output_buffer, frames_to_samples(to_pop));
|
||||||
|
|
||||||
|
return to_pop;
|
||||||
|
}
|
||||||
|
/** Returns the number of frames one needs to input into the delay line to get
|
||||||
|
* #frames_needed frames back.
|
||||||
|
* @parameter frames_needed the number of frames one want to write into the
|
||||||
|
* delay_line
|
||||||
|
* @returns the number of frames one will get. */
|
||||||
|
size_t input_needed_for_output(uint32_t frames_needed)
|
||||||
|
{
|
||||||
|
return frames_needed;
|
||||||
|
}
|
||||||
|
/** Returns the number of frames produces for `input_frames` frames in input */
|
||||||
|
size_t output_for_input(uint32_t input_frames)
|
||||||
|
{
|
||||||
|
return input_frames;
|
||||||
|
}
|
||||||
|
/** The number of frames this delay line delays the stream by.
|
||||||
|
* @returns The number of frames of delay. */
|
||||||
|
size_t latency()
|
||||||
|
{
|
||||||
|
return length;
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
/** The length, in frames, of this delay line */
|
||||||
|
uint32_t length;
|
||||||
|
/** When `input_buffer` is called, this allows tracking the number of samples
|
||||||
|
that where in the buffer. */
|
||||||
|
uint32_t leftover_samples;
|
||||||
|
/** The input buffer, where the delay is applied. */
|
||||||
|
auto_array<T> delay_input_buffer;
|
||||||
|
/** The output buffer. This is only ever used if using the ::output with a
|
||||||
|
* single argument. */
|
||||||
|
auto_array<T> delay_output_buffer;
|
||||||
|
};
|
||||||
|
|
||||||
|
/** This sits behind the C API and is more typed. */
|
||||||
|
template<typename T>
|
||||||
|
cubeb_resampler *
|
||||||
|
cubeb_resampler_create_internal(cubeb_stream * stream,
|
||||||
|
cubeb_stream_params * input_params,
|
||||||
|
cubeb_stream_params * output_params,
|
||||||
|
unsigned int target_rate,
|
||||||
|
cubeb_data_callback callback,
|
||||||
|
void * user_ptr,
|
||||||
|
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>> output_resampler = nullptr;
|
||||||
|
std::unique_ptr<delay_line<T>> input_delay = nullptr;
|
||||||
|
std::unique_ptr<delay_line<T>> output_delay = nullptr;
|
||||||
|
|
||||||
|
assert((input_params || output_params) &&
|
||||||
|
"need at least one valid parameter pointer.");
|
||||||
|
|
||||||
|
/* All the streams we have have a sample rate that matches the target
|
||||||
|
sample rate, use a no-op resampler, that simply forwards the buffers to the
|
||||||
|
callback. */
|
||||||
|
if (((input_params && input_params->rate == target_rate) &&
|
||||||
|
(output_params && output_params->rate == target_rate)) ||
|
||||||
|
(input_params && !output_params && (input_params->rate == target_rate)) ||
|
||||||
|
(output_params && !input_params && (output_params->rate == target_rate))) {
|
||||||
|
return new passthrough_resampler<T>(stream, callback,
|
||||||
|
user_ptr,
|
||||||
|
input_params ? input_params->channels : 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Determine if we need to resampler one or both directions, and create the
|
||||||
|
resamplers. */
|
||||||
|
if (output_params && (output_params->rate != target_rate)) {
|
||||||
|
output_resampler.reset(
|
||||||
|
new cubeb_resampler_speex_one_way<T>(output_params->channels,
|
||||||
|
target_rate,
|
||||||
|
output_params->rate,
|
||||||
|
to_speex_quality(quality)));
|
||||||
|
if (!output_resampler) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (input_params && (input_params->rate != target_rate)) {
|
||||||
|
input_resampler.reset(
|
||||||
|
new cubeb_resampler_speex_one_way<T>(input_params->channels,
|
||||||
|
input_params->rate,
|
||||||
|
target_rate,
|
||||||
|
to_speex_quality(quality)));
|
||||||
|
if (!input_resampler) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* If we resample only one direction but we have a duplex stream, insert a
|
||||||
|
* delay line with a length equal to the resampler latency of the
|
||||||
|
* other direction so that the streams are synchronized. */
|
||||||
|
if (input_resampler && !output_resampler && input_params && output_params) {
|
||||||
|
output_delay.reset(new delay_line<T>(input_resampler->latency(),
|
||||||
|
output_params->channels));
|
||||||
|
if (!output_delay) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
} else if (output_resampler && !input_resampler && input_params && output_params) {
|
||||||
|
input_delay.reset(new delay_line<T>(output_resampler->latency(),
|
||||||
|
input_params->channels));
|
||||||
|
if (!input_delay) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (input_resampler && output_resampler) {
|
||||||
|
return new cubeb_resampler_speex<T,
|
||||||
|
cubeb_resampler_speex_one_way<T>,
|
||||||
|
cubeb_resampler_speex_one_way<T>>
|
||||||
|
(input_resampler.release(),
|
||||||
|
output_resampler.release(),
|
||||||
|
stream, callback, user_ptr);
|
||||||
|
} else if (input_resampler) {
|
||||||
|
return new cubeb_resampler_speex<T,
|
||||||
|
cubeb_resampler_speex_one_way<T>,
|
||||||
|
delay_line<T>>
|
||||||
|
(input_resampler.release(),
|
||||||
|
output_delay.release(),
|
||||||
|
stream, callback, user_ptr);
|
||||||
|
} else {
|
||||||
|
return new cubeb_resampler_speex<T,
|
||||||
|
delay_line<T>,
|
||||||
|
cubeb_resampler_speex_one_way<T>>
|
||||||
|
(input_delay.release(),
|
||||||
|
output_resampler.release(),
|
||||||
|
stream, callback, user_ptr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* CUBEB_RESAMPLER_INTERNAL */
|
|
@ -0,0 +1,159 @@
|
||||||
|
/*
|
||||||
|
* Copyright © 2016 Mozilla Foundation
|
||||||
|
*
|
||||||
|
* This program is made available under an ISC-style license. See the
|
||||||
|
* accompanying file LICENSE for details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef CUBEB_RING_ARRAY_H
|
||||||
|
#define CUBEB_RING_ARRAY_H
|
||||||
|
|
||||||
|
#include "cubeb_utils.h"
|
||||||
|
|
||||||
|
/** Ring array of pointers is used to hold buffers. In case that
|
||||||
|
asynchronous producer/consumer callbacks do not arrive in a
|
||||||
|
repeated order the ring array stores the buffers and fetch
|
||||||
|
them in the correct order. */
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
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 count; /**< Number of elements in the array. */
|
||||||
|
unsigned int capacity; /**< Total length of the array. */
|
||||||
|
} ring_array;
|
||||||
|
|
||||||
|
static int
|
||||||
|
single_audiobuffer_init(AudioBuffer * buffer,
|
||||||
|
uint32_t bytesPerFrame,
|
||||||
|
uint32_t channelsPerFrame,
|
||||||
|
uint32_t frames)
|
||||||
|
{
|
||||||
|
assert(buffer);
|
||||||
|
assert(bytesPerFrame > 0 && channelsPerFrame && frames > 0);
|
||||||
|
|
||||||
|
size_t size = bytesPerFrame * frames;
|
||||||
|
buffer->mData = operator new(size);
|
||||||
|
if (buffer->mData == NULL) {
|
||||||
|
return CUBEB_ERROR;
|
||||||
|
}
|
||||||
|
PodZero(static_cast<char*>(buffer->mData), size);
|
||||||
|
|
||||||
|
buffer->mNumberChannels = channelsPerFrame;
|
||||||
|
buffer->mDataByteSize = size;
|
||||||
|
|
||||||
|
return CUBEB_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Initialize the ring array.
|
||||||
|
@param ra The ring_array pointer of allocated structure.
|
||||||
|
@retval 0 on success. */
|
||||||
|
int
|
||||||
|
ring_array_init(ring_array * ra,
|
||||||
|
uint32_t capacity,
|
||||||
|
uint32_t bytesPerFrame,
|
||||||
|
uint32_t channelsPerFrame,
|
||||||
|
uint32_t framesPerBuffer)
|
||||||
|
{
|
||||||
|
assert(ra);
|
||||||
|
if (capacity == 0 || bytesPerFrame == 0 ||
|
||||||
|
channelsPerFrame == 0 || framesPerBuffer == 0) {
|
||||||
|
return CUBEB_ERROR_INVALID_PARAMETER;
|
||||||
|
}
|
||||||
|
ra->capacity = capacity;
|
||||||
|
ra->tail = 0;
|
||||||
|
ra->count = 0;
|
||||||
|
|
||||||
|
ra->buffer_array = new AudioBuffer[ra->capacity];
|
||||||
|
PodZero(ra->buffer_array, ra->capacity);
|
||||||
|
if (ra->buffer_array == NULL) {
|
||||||
|
return CUBEB_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (unsigned int i = 0; i < ra->capacity; ++i) {
|
||||||
|
if (single_audiobuffer_init(&ra->buffer_array[i],
|
||||||
|
bytesPerFrame,
|
||||||
|
channelsPerFrame,
|
||||||
|
framesPerBuffer) != CUBEB_OK) {
|
||||||
|
return CUBEB_ERROR;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return CUBEB_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Destroy the ring array.
|
||||||
|
@param ra The ring_array pointer.*/
|
||||||
|
void
|
||||||
|
ring_array_destroy(ring_array * ra)
|
||||||
|
{
|
||||||
|
assert(ra);
|
||||||
|
if (ra->buffer_array == NULL){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (unsigned int i = 0; i < ra->capacity; ++i) {
|
||||||
|
if (ra->buffer_array[i].mData) {
|
||||||
|
operator delete(ra->buffer_array[i].mData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
delete [] ra->buffer_array;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Get the allocated buffer to be stored with fresh data.
|
||||||
|
@param ra The ring_array pointer.
|
||||||
|
@retval Pointer of the allocated space to be stored with fresh data or NULL if full. */
|
||||||
|
AudioBuffer *
|
||||||
|
ring_array_get_free_buffer(ring_array * ra)
|
||||||
|
{
|
||||||
|
assert(ra && ra->buffer_array);
|
||||||
|
assert(ra->buffer_array[0].mData != NULL);
|
||||||
|
if (ra->count == ra->capacity) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(ra->count == 0 || (ra->tail + ra->count) % ra->capacity != ra->tail);
|
||||||
|
AudioBuffer * ret = &ra->buffer_array[(ra->tail + ra->count) % ra->capacity];
|
||||||
|
|
||||||
|
++ra->count;
|
||||||
|
assert(ra->count <= ra->capacity);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Get the next available buffer with data.
|
||||||
|
@param ra The ring_array pointer.
|
||||||
|
@retval Pointer of the next in order data buffer or NULL if empty. */
|
||||||
|
AudioBuffer *
|
||||||
|
ring_array_get_data_buffer(ring_array * ra)
|
||||||
|
{
|
||||||
|
assert(ra && ra->buffer_array);
|
||||||
|
assert(ra->buffer_array[0].mData != NULL);
|
||||||
|
|
||||||
|
if (ra->count == 0) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
AudioBuffer * ret = &ra->buffer_array[ra->tail];
|
||||||
|
|
||||||
|
ra->tail = (ra->tail + 1) % ra->capacity;
|
||||||
|
assert(ra->tail < ra->capacity);
|
||||||
|
|
||||||
|
assert(ra->count > 0);
|
||||||
|
--ra->count;
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** When array is empty get the first allocated buffer in the array.
|
||||||
|
@param ra The ring_array pointer.
|
||||||
|
@retval If arrays is empty, pointer of the allocated space else NULL. */
|
||||||
|
AudioBuffer *
|
||||||
|
ring_array_get_dummy_buffer(ring_array * ra)
|
||||||
|
{
|
||||||
|
assert(ra && ra->buffer_array);
|
||||||
|
assert(ra->capacity > 0);
|
||||||
|
if (ra->count > 0) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
return &ra->buffer_array[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif //CUBEB_RING_ARRAY_H
|
|
@ -0,0 +1,484 @@
|
||||||
|
/*
|
||||||
|
* Copyright © 2016 Mozilla Foundation
|
||||||
|
*
|
||||||
|
* This program is made available under an ISC-style license. See the
|
||||||
|
* accompanying file LICENSE for details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef CUBEB_RING_BUFFER_H
|
||||||
|
#define CUBEB_RING_BUFFER_H
|
||||||
|
|
||||||
|
#include "cubeb_utils.h"
|
||||||
|
#include <algorithm>
|
||||||
|
#include <atomic>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <memory>
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Single producer single consumer lock-free and wait-free ring buffer.
|
||||||
|
*
|
||||||
|
* This data structure allows producing data from one thread, and consuming it on
|
||||||
|
* another thread, safely and without explicit synchronization. If used on two
|
||||||
|
* threads, this data structure uses atomics for thread safety. It is possible
|
||||||
|
* to disable the use of atomics at compile time and only use this data
|
||||||
|
* structure on one thread.
|
||||||
|
*
|
||||||
|
* The role for the producer and the consumer must be constant, i.e., the
|
||||||
|
* producer should always be on one thread and the consumer should always be on
|
||||||
|
* another thread.
|
||||||
|
*
|
||||||
|
* Some words about the inner workings of this class:
|
||||||
|
* - Capacity is fixed. Only one allocation is performed, in the constructor.
|
||||||
|
* When reading and writing, the return value of the method allows checking if
|
||||||
|
* the ring buffer is empty or full.
|
||||||
|
* - We always keep the read index at least one element ahead of the write
|
||||||
|
* index, so we can distinguish between an empty and a full ring buffer: an
|
||||||
|
* empty ring buffer is when the write index is at the same position as the
|
||||||
|
* read index. A full buffer is when the write index is exactly one position
|
||||||
|
* before the read index.
|
||||||
|
* - We synchronize updates to the read index after having read the data, and
|
||||||
|
* the write index after having written the data. This means that the each
|
||||||
|
* thread can only touch a portion of the buffer that is not touched by the
|
||||||
|
* other thread.
|
||||||
|
* - Callers are expected to provide buffers. When writing to the queue,
|
||||||
|
* elements are copied into the internal storage from the buffer passed in.
|
||||||
|
* When reading from the queue, the user is expected to provide a buffer.
|
||||||
|
* Because this is a ring buffer, data might not be contiguous in memory,
|
||||||
|
* providing an external buffer to copy into is an easy way to have linear
|
||||||
|
* data for further processing.
|
||||||
|
*/
|
||||||
|
template <typename T>
|
||||||
|
class ring_buffer_base
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* Constructor for a ring buffer.
|
||||||
|
*
|
||||||
|
* This performs an allocation, but is the only allocation that will happen
|
||||||
|
* for the life time of a `ring_buffer_base`.
|
||||||
|
*
|
||||||
|
* @param capacity The maximum number of element this ring buffer will hold.
|
||||||
|
*/
|
||||||
|
ring_buffer_base(int capacity)
|
||||||
|
/* One more element to distinguish from empty and full buffer. */
|
||||||
|
: capacity_(capacity + 1)
|
||||||
|
{
|
||||||
|
assert(storage_capacity() <
|
||||||
|
std::numeric_limits<int>::max() / 2 &&
|
||||||
|
"buffer too large for the type of index used.");
|
||||||
|
assert(capacity_ > 0);
|
||||||
|
|
||||||
|
data_.reset(new T[storage_capacity()]);
|
||||||
|
/* If this queue is using atomics, initializing those members as the last
|
||||||
|
* action in the constructor acts as a full barrier, and allow capacity() to
|
||||||
|
* be thread-safe. */
|
||||||
|
write_index_ = 0;
|
||||||
|
read_index_ = 0;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Push `count` zero or default constructed elements in the array.
|
||||||
|
*
|
||||||
|
* Only safely called on the producer thread.
|
||||||
|
*
|
||||||
|
* @param count The number of elements to enqueue.
|
||||||
|
* @return The number of element enqueued.
|
||||||
|
*/
|
||||||
|
int enqueue_default(int count)
|
||||||
|
{
|
||||||
|
return enqueue(nullptr, count);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* @brief Put an element in the queue
|
||||||
|
*
|
||||||
|
* Only safely called on the producer thread.
|
||||||
|
*
|
||||||
|
* @param element The element to put in the queue.
|
||||||
|
*
|
||||||
|
* @return 1 if the element was inserted, 0 otherwise.
|
||||||
|
*/
|
||||||
|
int enqueue(T& element)
|
||||||
|
{
|
||||||
|
return enqueue(&element, 1);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Push `count` elements in the ring buffer.
|
||||||
|
*
|
||||||
|
* Only safely called on the producer thread.
|
||||||
|
*
|
||||||
|
* @param elements a pointer to a buffer containing at least `count` elements.
|
||||||
|
* If `elements` is nullptr, zero or default constructed elements are enqueued.
|
||||||
|
* @param count The number of elements to read from `elements`
|
||||||
|
* @return The number of elements successfully coped from `elements` and inserted
|
||||||
|
* into the ring buffer.
|
||||||
|
*/
|
||||||
|
int enqueue(T * elements, int count)
|
||||||
|
{
|
||||||
|
#ifndef NDEBUG
|
||||||
|
assert_correct_thread(producer_id);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
int rd_idx = read_index_.load(std::memory_order::memory_order_relaxed);
|
||||||
|
int wr_idx = write_index_.load(std::memory_order::memory_order_relaxed);
|
||||||
|
|
||||||
|
if (full_internal(rd_idx, wr_idx)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int to_write =
|
||||||
|
std::min(available_write_internal(rd_idx, wr_idx), count);
|
||||||
|
|
||||||
|
/* First part, from the write index to the end of the array. */
|
||||||
|
int first_part = std::min(storage_capacity() - wr_idx,
|
||||||
|
to_write);
|
||||||
|
/* Second part, from the beginning of the array */
|
||||||
|
int second_part = to_write - first_part;
|
||||||
|
|
||||||
|
if (elements) {
|
||||||
|
Copy(data_.get() + wr_idx, elements, first_part);
|
||||||
|
Copy(data_.get(), elements + first_part, second_part);
|
||||||
|
} else {
|
||||||
|
ConstructDefault(data_.get() + wr_idx, first_part);
|
||||||
|
ConstructDefault(data_.get(), second_part);
|
||||||
|
}
|
||||||
|
|
||||||
|
write_index_.store(increment_index(wr_idx, to_write), std::memory_order::memory_order_release);
|
||||||
|
|
||||||
|
return to_write;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Retrieve at most `count` elements from the ring buffer, and copy them to
|
||||||
|
* `elements`, if non-null.
|
||||||
|
*
|
||||||
|
* Only safely called on the consumer side.
|
||||||
|
*
|
||||||
|
* @param elements A pointer to a buffer with space for at least `count`
|
||||||
|
* elements. If `elements` is `nullptr`, `count` element will be discarded.
|
||||||
|
* @param count The maximum number of elements to dequeue.
|
||||||
|
* @return The number of elements written to `elements`.
|
||||||
|
*/
|
||||||
|
int dequeue(T * elements, int count)
|
||||||
|
{
|
||||||
|
#ifndef NDEBUG
|
||||||
|
assert_correct_thread(consumer_id);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
int wr_idx = write_index_.load(std::memory_order::memory_order_acquire);
|
||||||
|
int rd_idx = read_index_.load(std::memory_order::memory_order_relaxed);
|
||||||
|
|
||||||
|
if (empty_internal(rd_idx, wr_idx)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int to_read =
|
||||||
|
std::min(available_read_internal(rd_idx, wr_idx), count);
|
||||||
|
|
||||||
|
int first_part = std::min(storage_capacity() - rd_idx, to_read);
|
||||||
|
int second_part = to_read - first_part;
|
||||||
|
|
||||||
|
if (elements) {
|
||||||
|
Copy(elements, data_.get() + rd_idx, first_part);
|
||||||
|
Copy(elements + first_part, data_.get(), second_part);
|
||||||
|
}
|
||||||
|
|
||||||
|
read_index_.store(increment_index(rd_idx, to_read), std::memory_order::memory_order_relaxed);
|
||||||
|
|
||||||
|
return to_read;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Get the number of available element for consuming.
|
||||||
|
*
|
||||||
|
* Only safely called on the consumer thread.
|
||||||
|
*
|
||||||
|
* @return The number of available elements for reading.
|
||||||
|
*/
|
||||||
|
int available_read() const
|
||||||
|
{
|
||||||
|
#ifndef NDEBUG
|
||||||
|
assert_correct_thread(consumer_id);
|
||||||
|
#endif
|
||||||
|
return available_read_internal(read_index_.load(std::memory_order::memory_order_relaxed),
|
||||||
|
write_index_.load(std::memory_order::memory_order_relaxed));
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Get the number of available elements for consuming.
|
||||||
|
*
|
||||||
|
* Only safely called on the producer thread.
|
||||||
|
*
|
||||||
|
* @return The number of empty slots in the buffer, available for writing.
|
||||||
|
*/
|
||||||
|
int available_write() const
|
||||||
|
{
|
||||||
|
#ifndef NDEBUG
|
||||||
|
assert_correct_thread(producer_id);
|
||||||
|
#endif
|
||||||
|
return available_write_internal(read_index_.load(std::memory_order::memory_order_relaxed),
|
||||||
|
write_index_.load(std::memory_order::memory_order_relaxed));
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Get the total capacity, for this ring buffer.
|
||||||
|
*
|
||||||
|
* Can be called safely on any thread.
|
||||||
|
*
|
||||||
|
* @return The maximum capacity of this ring buffer.
|
||||||
|
*/
|
||||||
|
int capacity() const
|
||||||
|
{
|
||||||
|
return storage_capacity() - 1;
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
/** Return true if the ring buffer is empty.
|
||||||
|
*
|
||||||
|
* @param read_index the read index to consider
|
||||||
|
* @param write_index the write index to consider
|
||||||
|
* @return true if the ring buffer is empty, false otherwise.
|
||||||
|
**/
|
||||||
|
bool empty_internal(int read_index,
|
||||||
|
int write_index) const
|
||||||
|
{
|
||||||
|
return write_index == read_index;
|
||||||
|
}
|
||||||
|
/** Return true if the ring buffer is full.
|
||||||
|
*
|
||||||
|
* This happens if the write index is exactly one element behind the read
|
||||||
|
* index.
|
||||||
|
*
|
||||||
|
* @param read_index the read index to consider
|
||||||
|
* @param write_index the write index to consider
|
||||||
|
* @return true if the ring buffer is full, false otherwise.
|
||||||
|
**/
|
||||||
|
bool full_internal(int read_index,
|
||||||
|
int write_index) const
|
||||||
|
{
|
||||||
|
return (write_index + 1) % storage_capacity() == read_index;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Return the size of the storage. It is one more than 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
|
||||||
|
{
|
||||||
|
return capacity_;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Returns the number of elements available for reading.
|
||||||
|
*
|
||||||
|
* @return the number of available elements for reading.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
available_read_internal(int read_index,
|
||||||
|
int write_index) const
|
||||||
|
{
|
||||||
|
if (write_index >= read_index) {
|
||||||
|
return write_index - read_index;
|
||||||
|
} else {
|
||||||
|
return write_index + storage_capacity() - read_index;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Returns the number of empty elements, available for writing.
|
||||||
|
*
|
||||||
|
* @return the number of elements that can be written into the array.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
available_write_internal(int read_index,
|
||||||
|
int write_index) const
|
||||||
|
{
|
||||||
|
/* We substract one element here to always keep at least one sample
|
||||||
|
* free in the buffer, to distinguish between full and empty array. */
|
||||||
|
int rv = read_index - write_index - 1;
|
||||||
|
if (write_index >= read_index) {
|
||||||
|
rv += storage_capacity();
|
||||||
|
}
|
||||||
|
return rv;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Increments an index, wrapping it around the storage.
|
||||||
|
*
|
||||||
|
* @param index a reference to the index to increment.
|
||||||
|
* @param increment the number by which `index` is incremented.
|
||||||
|
* @return the new index.
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
increment_index(int index, int increment) const
|
||||||
|
{
|
||||||
|
assert(increment >= 0);
|
||||||
|
return (index + increment) % storage_capacity();
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* @brief This allows checking that enqueue (resp. dequeue) are always called
|
||||||
|
* by the right thread.
|
||||||
|
*
|
||||||
|
* @param id the id of the thread that has called the calling method first.
|
||||||
|
*/
|
||||||
|
#ifndef NDEBUG
|
||||||
|
static void assert_correct_thread(std::thread::id& id)
|
||||||
|
{
|
||||||
|
if (id == std::thread::id()) {
|
||||||
|
id = std::this_thread::get_id();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
assert(id == std::this_thread::get_id());
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
/** Index at which the oldest element is at, in samples. */
|
||||||
|
std::atomic<int> read_index_;
|
||||||
|
/** Index at which to write new elements. `write_index` is always at
|
||||||
|
* least one element ahead of `read_index_`. */
|
||||||
|
std::atomic<int> write_index_;
|
||||||
|
/** Maximum number of elements that can be stored in the ring buffer. */
|
||||||
|
const int capacity_;
|
||||||
|
/** Data storage */
|
||||||
|
std::unique_ptr<T[]> data_;
|
||||||
|
#ifndef NDEBUG
|
||||||
|
/** The id of the only thread that is allowed to read from the queue. */
|
||||||
|
mutable std::thread::id consumer_id;
|
||||||
|
/** The id of the only thread that is allowed to write from the queue. */
|
||||||
|
mutable std::thread::id producer_id;
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adapter for `ring_buffer_base` that exposes an interface in frames.
|
||||||
|
*/
|
||||||
|
template <typename T>
|
||||||
|
class audio_ring_buffer_base
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* @brief Constructor.
|
||||||
|
*
|
||||||
|
* @param channel_count Number of channels.
|
||||||
|
* @param capacity_in_frames The capacity in frames.
|
||||||
|
*/
|
||||||
|
audio_ring_buffer_base(int channel_count, int capacity_in_frames)
|
||||||
|
: channel_count(channel_count)
|
||||||
|
, ring_buffer(frames_to_samples(capacity_in_frames))
|
||||||
|
{
|
||||||
|
assert(channel_count > 0);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* @brief Enqueue silence.
|
||||||
|
*
|
||||||
|
* Only safely called on the producer thread.
|
||||||
|
*
|
||||||
|
* @param frame_count The number of frames of silence to enqueue.
|
||||||
|
* @return The number of frames of silence actually written to the queue.
|
||||||
|
*/
|
||||||
|
int enqueue_default(int frame_count)
|
||||||
|
{
|
||||||
|
return samples_to_frames(ring_buffer.enqueue(nullptr, frames_to_samples(frame_count)));
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* @brief Enqueue `frames_count` frames of audio.
|
||||||
|
*
|
||||||
|
* Only safely called from the producer thread.
|
||||||
|
*
|
||||||
|
* @param [in] frames If non-null, the frames to enqueue.
|
||||||
|
* Otherwise, silent frames are enqueued.
|
||||||
|
* @param frame_count The number of frames to enqueue.
|
||||||
|
*
|
||||||
|
* @return The number of frames enqueued
|
||||||
|
*/
|
||||||
|
|
||||||
|
int enqueue(T * frames, int frame_count)
|
||||||
|
{
|
||||||
|
return samples_to_frames(ring_buffer.enqueue(frames, frames_to_samples(frame_count)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Removes `frame_count` frames from the buffer, and
|
||||||
|
* write them to `frames` if it is non-null.
|
||||||
|
*
|
||||||
|
* Only safely called on the consumer thread.
|
||||||
|
*
|
||||||
|
* @param frames If non-null, the frames are copied to `frames`.
|
||||||
|
* Otherwise, they are dropped.
|
||||||
|
* @param frame_count The number of frames to remove.
|
||||||
|
*
|
||||||
|
* @return The number of frames actually dequeud.
|
||||||
|
*/
|
||||||
|
int dequeue(T * frames, int 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.
|
||||||
|
*
|
||||||
|
* Only safely called on the consumer thread.
|
||||||
|
*
|
||||||
|
* @return The number of available frames of audio for reading.
|
||||||
|
*/
|
||||||
|
int available_read() const
|
||||||
|
{
|
||||||
|
return samples_to_frames(ring_buffer.available_read());
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Get the number of available frames of audio for consuming.
|
||||||
|
*
|
||||||
|
* Only safely called on the producer thread.
|
||||||
|
*
|
||||||
|
* @return The number of empty slots in the buffer, available for writing.
|
||||||
|
*/
|
||||||
|
int available_write() const
|
||||||
|
{
|
||||||
|
return samples_to_frames(ring_buffer.available_write());
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Get the total capacity, for this ring buffer.
|
||||||
|
*
|
||||||
|
* Can be called safely on any thread.
|
||||||
|
*
|
||||||
|
* @return The maximum capacity of this ring buffer.
|
||||||
|
*/
|
||||||
|
int capacity() const
|
||||||
|
{
|
||||||
|
return samples_to_frames(ring_buffer.capacity());
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
/**
|
||||||
|
* @brief Frames to samples conversion.
|
||||||
|
*
|
||||||
|
* @param frames The number of frames.
|
||||||
|
*
|
||||||
|
* @return A number of samples.
|
||||||
|
*/
|
||||||
|
int frames_to_samples(int frames) const
|
||||||
|
{
|
||||||
|
return frames * channel_count;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* @brief Samples to frames conversion.
|
||||||
|
*
|
||||||
|
* @param samples The number of samples.
|
||||||
|
*
|
||||||
|
* @return A number of frames.
|
||||||
|
*/
|
||||||
|
int samples_to_frames(int samples) const
|
||||||
|
{
|
||||||
|
return samples / channel_count;
|
||||||
|
}
|
||||||
|
/** Number of channels of audio that will stream through this ring buffer. */
|
||||||
|
int channel_count;
|
||||||
|
/** The underlying ring buffer that is used to store the data. */
|
||||||
|
ring_buffer_base<T> ring_buffer;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lock-free instantiation of the `ring_buffer_base` type. This is safe to use
|
||||||
|
* from two threads, one producer, one consumer (that never change role),
|
||||||
|
* without explicit synchronization.
|
||||||
|
*/
|
||||||
|
template<typename T>
|
||||||
|
using lock_free_queue = ring_buffer_base<T>;
|
||||||
|
/**
|
||||||
|
* 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),
|
||||||
|
* without explicit synchronization.
|
||||||
|
*/
|
||||||
|
template<typename T>
|
||||||
|
using lock_free_audio_ring_buffer = audio_ring_buffer_base<T>;
|
||||||
|
|
||||||
|
#endif // CUBEB_RING_BUFFER_H
|
|
@ -0,0 +1,387 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2011 Alexandre Ratchov <alex@caoua.org>
|
||||||
|
*
|
||||||
|
* This program is made available under an ISC-style license. See the
|
||||||
|
* accompanying file LICENSE for details.
|
||||||
|
*/
|
||||||
|
#include <math.h>
|
||||||
|
#include <poll.h>
|
||||||
|
#include <pthread.h>
|
||||||
|
#include <sndio.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <assert.h>
|
||||||
|
#include "cubeb/cubeb.h"
|
||||||
|
#include "cubeb-internal.h"
|
||||||
|
|
||||||
|
#if defined(CUBEB_SNDIO_DEBUG)
|
||||||
|
#define DPR(...) fprintf(stderr, __VA_ARGS__);
|
||||||
|
#else
|
||||||
|
#define DPR(...) do {} while(0)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static struct cubeb_ops const sndio_ops;
|
||||||
|
|
||||||
|
struct cubeb {
|
||||||
|
struct cubeb_ops const * ops;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct cubeb_stream {
|
||||||
|
cubeb * context;
|
||||||
|
pthread_t th; /* to run real-time audio i/o */
|
||||||
|
pthread_mutex_t mtx; /* protects hdl and pos */
|
||||||
|
struct sio_hdl *hdl; /* link us to sndio */
|
||||||
|
int active; /* cubec_start() called */
|
||||||
|
int conv; /* need float->s16 conversion */
|
||||||
|
unsigned char *buf; /* data is prepared here */
|
||||||
|
unsigned int nfr; /* number of frames in buf */
|
||||||
|
unsigned int bpf; /* bytes per frame */
|
||||||
|
unsigned int pchan; /* number of play channels */
|
||||||
|
uint64_t rdpos; /* frame number Joe hears right now */
|
||||||
|
uint64_t wrpos; /* number of written frames */
|
||||||
|
cubeb_data_callback data_cb; /* cb to preapare data */
|
||||||
|
cubeb_state_callback state_cb; /* cb to notify about state changes */
|
||||||
|
void *arg; /* user arg to {data,state}_cb */
|
||||||
|
};
|
||||||
|
|
||||||
|
static void
|
||||||
|
float_to_s16(void *ptr, long nsamp)
|
||||||
|
{
|
||||||
|
int16_t *dst = ptr;
|
||||||
|
float *src = ptr;
|
||||||
|
int s;
|
||||||
|
|
||||||
|
while (nsamp-- > 0) {
|
||||||
|
s = lrintf(*(src++) * 32768);
|
||||||
|
if (s < -32768)
|
||||||
|
s = -32768;
|
||||||
|
else if (s > 32767)
|
||||||
|
s = 32767;
|
||||||
|
*(dst++) = s;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
sndio_onmove(void *arg, int delta)
|
||||||
|
{
|
||||||
|
cubeb_stream *s = (cubeb_stream *)arg;
|
||||||
|
|
||||||
|
s->rdpos += delta * s->bpf;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void *
|
||||||
|
sndio_mainloop(void *arg)
|
||||||
|
{
|
||||||
|
#define MAXFDS 8
|
||||||
|
struct pollfd pfds[MAXFDS];
|
||||||
|
cubeb_stream *s = arg;
|
||||||
|
int n, nfds, revents, state = CUBEB_STATE_STARTED;
|
||||||
|
size_t start = 0, end = 0;
|
||||||
|
long nfr;
|
||||||
|
|
||||||
|
DPR("sndio_mainloop()\n");
|
||||||
|
s->state_cb(s, s->arg, CUBEB_STATE_STARTED);
|
||||||
|
pthread_mutex_lock(&s->mtx);
|
||||||
|
if (!sio_start(s->hdl)) {
|
||||||
|
pthread_mutex_unlock(&s->mtx);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
DPR("sndio_mainloop(), started\n");
|
||||||
|
|
||||||
|
start = end = s->nfr;
|
||||||
|
for (;;) {
|
||||||
|
if (!s->active) {
|
||||||
|
DPR("sndio_mainloop() stopped\n");
|
||||||
|
state = CUBEB_STATE_STOPPED;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (start == end) {
|
||||||
|
if (end < s->nfr) {
|
||||||
|
DPR("sndio_mainloop() drained\n");
|
||||||
|
state = CUBEB_STATE_DRAINED;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
pthread_mutex_unlock(&s->mtx);
|
||||||
|
nfr = s->data_cb(s, s->arg, NULL, s->buf, s->nfr);
|
||||||
|
pthread_mutex_lock(&s->mtx);
|
||||||
|
if (nfr < 0) {
|
||||||
|
DPR("sndio_mainloop() cb err\n");
|
||||||
|
state = CUBEB_STATE_ERROR;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (s->conv)
|
||||||
|
float_to_s16(s->buf, nfr * s->pchan);
|
||||||
|
start = 0;
|
||||||
|
end = nfr * s->bpf;
|
||||||
|
}
|
||||||
|
if (end == 0)
|
||||||
|
continue;
|
||||||
|
nfds = sio_pollfd(s->hdl, pfds, POLLOUT);
|
||||||
|
if (nfds > 0) {
|
||||||
|
pthread_mutex_unlock(&s->mtx);
|
||||||
|
n = poll(pfds, nfds, -1);
|
||||||
|
pthread_mutex_lock(&s->mtx);
|
||||||
|
if (n < 0)
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
revents = sio_revents(s->hdl, pfds);
|
||||||
|
if (revents & POLLHUP)
|
||||||
|
break;
|
||||||
|
if (revents & POLLOUT) {
|
||||||
|
n = sio_write(s->hdl, s->buf + start, end - start);
|
||||||
|
if (n == 0) {
|
||||||
|
DPR("sndio_mainloop() werr\n");
|
||||||
|
state = CUBEB_STATE_ERROR;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
s->wrpos += n;
|
||||||
|
start += n;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sio_stop(s->hdl);
|
||||||
|
s->rdpos = s->wrpos;
|
||||||
|
pthread_mutex_unlock(&s->mtx);
|
||||||
|
s->state_cb(s, s->arg, state);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*static*/ int
|
||||||
|
sndio_init(cubeb **context, char const *context_name)
|
||||||
|
{
|
||||||
|
DPR("sndio_init(%s)\n", context_name);
|
||||||
|
*context = malloc(sizeof(*context));
|
||||||
|
(*context)->ops = &sndio_ops;
|
||||||
|
(void)context_name;
|
||||||
|
return CUBEB_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
static char const *
|
||||||
|
sndio_get_backend_id(cubeb *context)
|
||||||
|
{
|
||||||
|
return "sndio";
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
sndio_destroy(cubeb *context)
|
||||||
|
{
|
||||||
|
DPR("sndio_destroy()\n");
|
||||||
|
free(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
sndio_stream_init(cubeb * context,
|
||||||
|
cubeb_stream ** stream,
|
||||||
|
char const * stream_name,
|
||||||
|
cubeb_devid input_device,
|
||||||
|
cubeb_stream_params * input_stream_params,
|
||||||
|
cubeb_devid output_device,
|
||||||
|
cubeb_stream_params * output_stream_params,
|
||||||
|
unsigned int latency_frames,
|
||||||
|
cubeb_data_callback data_callback,
|
||||||
|
cubeb_state_callback state_callback,
|
||||||
|
void *user_ptr)
|
||||||
|
{
|
||||||
|
cubeb_stream *s;
|
||||||
|
struct sio_par wpar, rpar;
|
||||||
|
DPR("sndio_stream_init(%s)\n", stream_name);
|
||||||
|
size_t size;
|
||||||
|
|
||||||
|
assert(!input_stream_params && "not supported.");
|
||||||
|
if (input_device || output_device) {
|
||||||
|
/* Device selection not yet implemented. */
|
||||||
|
return CUBEB_ERROR_DEVICE_UNAVAILABLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
s = malloc(sizeof(cubeb_stream));
|
||||||
|
if (s == NULL)
|
||||||
|
return CUBEB_ERROR;
|
||||||
|
s->context = context;
|
||||||
|
s->hdl = sio_open(NULL, SIO_PLAY, 1);
|
||||||
|
if (s->hdl == NULL) {
|
||||||
|
free(s);
|
||||||
|
DPR("sndio_stream_init(), sio_open() failed\n");
|
||||||
|
return CUBEB_ERROR;
|
||||||
|
}
|
||||||
|
sio_initpar(&wpar);
|
||||||
|
wpar.sig = 1;
|
||||||
|
wpar.bits = 16;
|
||||||
|
switch (output_stream_params->format) {
|
||||||
|
case CUBEB_SAMPLE_S16LE:
|
||||||
|
wpar.le = 1;
|
||||||
|
break;
|
||||||
|
case CUBEB_SAMPLE_S16BE:
|
||||||
|
wpar.le = 0;
|
||||||
|
break;
|
||||||
|
case CUBEB_SAMPLE_FLOAT32NE:
|
||||||
|
wpar.le = SIO_LE_NATIVE;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
sio_close(s->hdl);
|
||||||
|
free(s);
|
||||||
|
DPR("sndio_stream_init() unsupported format\n");
|
||||||
|
return CUBEB_ERROR_INVALID_FORMAT;
|
||||||
|
}
|
||||||
|
wpar.rate = output_stream_params->rate;
|
||||||
|
wpar.pchan = output_stream_params->channels;
|
||||||
|
wpar.appbufsz = latency_frames;
|
||||||
|
if (!sio_setpar(s->hdl, &wpar) || !sio_getpar(s->hdl, &rpar)) {
|
||||||
|
sio_close(s->hdl);
|
||||||
|
free(s);
|
||||||
|
DPR("sndio_stream_init(), sio_setpar() failed\n");
|
||||||
|
return CUBEB_ERROR;
|
||||||
|
}
|
||||||
|
if (rpar.bits != wpar.bits || rpar.le != wpar.le ||
|
||||||
|
rpar.sig != wpar.sig || rpar.rate != wpar.rate ||
|
||||||
|
rpar.pchan != wpar.pchan) {
|
||||||
|
sio_close(s->hdl);
|
||||||
|
free(s);
|
||||||
|
DPR("sndio_stream_init() unsupported params\n");
|
||||||
|
return CUBEB_ERROR_INVALID_FORMAT;
|
||||||
|
}
|
||||||
|
sio_onmove(s->hdl, sndio_onmove, s);
|
||||||
|
s->active = 0;
|
||||||
|
s->nfr = rpar.round;
|
||||||
|
s->bpf = rpar.bps * rpar.pchan;
|
||||||
|
s->pchan = rpar.pchan;
|
||||||
|
s->data_cb = data_callback;
|
||||||
|
s->state_cb = state_callback;
|
||||||
|
s->arg = user_ptr;
|
||||||
|
s->mtx = (pthread_mutex_t)PTHREAD_MUTEX_INITIALIZER;
|
||||||
|
s->rdpos = s->wrpos = 0;
|
||||||
|
if (output_stream_params->format == CUBEB_SAMPLE_FLOAT32LE) {
|
||||||
|
s->conv = 1;
|
||||||
|
size = rpar.round * rpar.pchan * sizeof(float);
|
||||||
|
} else {
|
||||||
|
s->conv = 0;
|
||||||
|
size = rpar.round * rpar.pchan * rpar.bps;
|
||||||
|
}
|
||||||
|
s->buf = malloc(size);
|
||||||
|
if (s->buf == NULL) {
|
||||||
|
sio_close(s->hdl);
|
||||||
|
free(s);
|
||||||
|
return CUBEB_ERROR;
|
||||||
|
}
|
||||||
|
*stream = s;
|
||||||
|
DPR("sndio_stream_init() end, ok\n");
|
||||||
|
(void)context;
|
||||||
|
(void)stream_name;
|
||||||
|
return CUBEB_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
sndio_get_max_channel_count(cubeb * ctx, uint32_t * max_channels)
|
||||||
|
{
|
||||||
|
assert(ctx && max_channels);
|
||||||
|
|
||||||
|
*max_channels = 8;
|
||||||
|
|
||||||
|
return CUBEB_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
sndio_get_preferred_sample_rate(cubeb * ctx, uint32_t * rate)
|
||||||
|
{
|
||||||
|
// XXX Not yet implemented.
|
||||||
|
*rate = 44100;
|
||||||
|
|
||||||
|
return CUBEB_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
sndio_get_min_latency(cubeb * ctx, cubeb_stream_params params, uint32_t * latency_frames)
|
||||||
|
{
|
||||||
|
// XXX Not yet implemented.
|
||||||
|
*latency_frames = 2048;
|
||||||
|
|
||||||
|
return CUBEB_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
sndio_stream_destroy(cubeb_stream *s)
|
||||||
|
{
|
||||||
|
DPR("sndio_stream_destroy()\n");
|
||||||
|
sio_close(s->hdl);
|
||||||
|
free(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
sndio_stream_start(cubeb_stream *s)
|
||||||
|
{
|
||||||
|
int err;
|
||||||
|
|
||||||
|
DPR("sndio_stream_start()\n");
|
||||||
|
s->active = 1;
|
||||||
|
err = pthread_create(&s->th, NULL, sndio_mainloop, s);
|
||||||
|
if (err) {
|
||||||
|
s->active = 0;
|
||||||
|
return CUBEB_ERROR;
|
||||||
|
}
|
||||||
|
return CUBEB_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
sndio_stream_stop(cubeb_stream *s)
|
||||||
|
{
|
||||||
|
void *dummy;
|
||||||
|
|
||||||
|
DPR("sndio_stream_stop()\n");
|
||||||
|
if (s->active) {
|
||||||
|
s->active = 0;
|
||||||
|
pthread_join(s->th, &dummy);
|
||||||
|
}
|
||||||
|
return CUBEB_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
sndio_stream_get_position(cubeb_stream *s, uint64_t *p)
|
||||||
|
{
|
||||||
|
pthread_mutex_lock(&s->mtx);
|
||||||
|
DPR("sndio_stream_get_position() %lld\n", s->rdpos);
|
||||||
|
*p = s->rdpos / s->bpf;
|
||||||
|
pthread_mutex_unlock(&s->mtx);
|
||||||
|
return CUBEB_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
sndio_stream_set_volume(cubeb_stream *s, float volume)
|
||||||
|
{
|
||||||
|
DPR("sndio_stream_set_volume(%f)\n", volume);
|
||||||
|
pthread_mutex_lock(&s->mtx);
|
||||||
|
sio_setvol(s->hdl, SIO_MAXVOL * volume);
|
||||||
|
pthread_mutex_unlock(&s->mtx);
|
||||||
|
return CUBEB_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
sndio_stream_get_latency(cubeb_stream * stm, uint32_t * latency)
|
||||||
|
{
|
||||||
|
// http://www.openbsd.org/cgi-bin/man.cgi?query=sio_open
|
||||||
|
// in the "Measuring the latency and buffers usage" paragraph.
|
||||||
|
*latency = (stm->wrpos - stm->rdpos) / stm->bpf;
|
||||||
|
return CUBEB_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct cubeb_ops const sndio_ops = {
|
||||||
|
.init = sndio_init,
|
||||||
|
.get_backend_id = sndio_get_backend_id,
|
||||||
|
.get_max_channel_count = sndio_get_max_channel_count,
|
||||||
|
.get_min_latency = sndio_get_min_latency,
|
||||||
|
.get_preferred_sample_rate = sndio_get_preferred_sample_rate,
|
||||||
|
.get_preferred_channel_layout = NULL,
|
||||||
|
.enumerate_devices = NULL,
|
||||||
|
.device_collection_destroy = NULL,
|
||||||
|
.destroy = sndio_destroy,
|
||||||
|
.stream_init = sndio_stream_init,
|
||||||
|
.stream_destroy = sndio_stream_destroy,
|
||||||
|
.stream_start = sndio_stream_start,
|
||||||
|
.stream_stop = sndio_stream_stop,
|
||||||
|
.stream_get_position = sndio_stream_get_position,
|
||||||
|
.stream_get_latency = sndio_stream_get_latency,
|
||||||
|
.stream_set_volume = sndio_stream_set_volume,
|
||||||
|
.stream_set_panning = NULL,
|
||||||
|
.stream_get_current_device = NULL,
|
||||||
|
.stream_device_destroy = NULL,
|
||||||
|
.stream_register_device_changed_callback = NULL,
|
||||||
|
.register_device_collection_changed = NULL
|
||||||
|
};
|
|
@ -0,0 +1,38 @@
|
||||||
|
/*
|
||||||
|
* Copyright © 2016 Mozilla Foundation
|
||||||
|
*
|
||||||
|
* This program is made available under an ISC-style license. See the
|
||||||
|
* accompanying file LICENSE for details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "cubeb_utils.h"
|
||||||
|
#include "cubeb_assert.h"
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
static void
|
||||||
|
device_info_destroy(cubeb_device_info * info)
|
||||||
|
{
|
||||||
|
XASSERT(info);
|
||||||
|
|
||||||
|
free((void *) info->device_id);
|
||||||
|
free((void *) info->friendly_name);
|
||||||
|
free((void *) info->group_id);
|
||||||
|
free((void *) info->vendor_name);
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
cubeb_utils_default_device_collection_destroy(cubeb * context,
|
||||||
|
cubeb_device_collection * collection)
|
||||||
|
{
|
||||||
|
uint32_t i;
|
||||||
|
XASSERT(collection);
|
||||||
|
|
||||||
|
(void) context;
|
||||||
|
|
||||||
|
for (i = 0; i < collection->count; i++)
|
||||||
|
device_info_destroy(&collection->device[i]);
|
||||||
|
|
||||||
|
free(collection->device);
|
||||||
|
return CUBEB_OK;
|
||||||
|
}
|
|
@ -0,0 +1,352 @@
|
||||||
|
/*
|
||||||
|
* Copyright © 2016 Mozilla Foundation
|
||||||
|
*
|
||||||
|
* This program is made available under an ISC-style license. See the
|
||||||
|
* accompanying file LICENSE for details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#if !defined(CUBEB_UTILS)
|
||||||
|
#define CUBEB_UTILS
|
||||||
|
|
||||||
|
#include "cubeb/cubeb.h"
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <assert.h>
|
||||||
|
#include <mutex>
|
||||||
|
#include <type_traits>
|
||||||
|
#if defined(WIN32)
|
||||||
|
#include "cubeb_utils_win.h"
|
||||||
|
#else
|
||||||
|
#include "cubeb_utils_unix.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/** Similar to memcpy, but accounts for the size of an element. */
|
||||||
|
template<typename T>
|
||||||
|
void PodCopy(T * destination, const T * source, size_t count)
|
||||||
|
{
|
||||||
|
static_assert(std::is_trivial<T>::value, "Requires trivial type");
|
||||||
|
assert(destination && source);
|
||||||
|
memcpy(destination, source, count * sizeof(T));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Similar to memmove, but accounts for the size of an element. */
|
||||||
|
template<typename T>
|
||||||
|
void PodMove(T * destination, const T * source, size_t count)
|
||||||
|
{
|
||||||
|
static_assert(std::is_trivial<T>::value, "Requires trivial type");
|
||||||
|
assert(destination && source);
|
||||||
|
memmove(destination, source, count * sizeof(T));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Similar to a memset to zero, but accounts for the size of an element. */
|
||||||
|
template<typename T>
|
||||||
|
void PodZero(T * destination, size_t count)
|
||||||
|
{
|
||||||
|
static_assert(std::is_trivial<T>::value, "Requires trivial type");
|
||||||
|
assert(destination);
|
||||||
|
memset(destination, 0, count * sizeof(T));
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
template<typename T, typename Trait>
|
||||||
|
void Copy(T * destination, const T * source, size_t count, Trait)
|
||||||
|
{
|
||||||
|
for (size_t i = 0; i < count; i++) {
|
||||||
|
destination[i] = source[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
void Copy(T * destination, const T * source, size_t count, std::true_type)
|
||||||
|
{
|
||||||
|
PodCopy(destination, source, count);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This allows copying a number of elements from a `source` pointer to a
|
||||||
|
* `destination` pointer, using `memcpy` if it is safe to do so, or a loop that
|
||||||
|
* calls the constructors and destructors otherwise.
|
||||||
|
*/
|
||||||
|
template<typename T>
|
||||||
|
void Copy(T * destination, const T * source, size_t count)
|
||||||
|
{
|
||||||
|
assert(destination && source);
|
||||||
|
Copy(destination, source, count, typename std::is_trivial<T>::type());
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
template<typename T, typename Trait>
|
||||||
|
void ConstructDefault(T * destination, size_t count, Trait)
|
||||||
|
{
|
||||||
|
for (size_t i = 0; i < count; i++) {
|
||||||
|
destination[i] = T();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
void ConstructDefault(T * destination,
|
||||||
|
size_t count, std::true_type)
|
||||||
|
{
|
||||||
|
PodZero(destination, count);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This allows zeroing (using memset) or default-constructing a number of
|
||||||
|
* elements calling the constructors and destructors if necessary.
|
||||||
|
*/
|
||||||
|
template<typename T>
|
||||||
|
void ConstructDefault(T * destination, size_t count)
|
||||||
|
{
|
||||||
|
assert(destination);
|
||||||
|
ConstructDefault(destination, count,
|
||||||
|
typename std::is_arithmetic<T>::type());
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
class auto_array
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit auto_array(uint32_t capacity = 0)
|
||||||
|
: data_(capacity ? new T[capacity] : nullptr)
|
||||||
|
, capacity_(capacity)
|
||||||
|
, length_(0)
|
||||||
|
{}
|
||||||
|
|
||||||
|
~auto_array()
|
||||||
|
{
|
||||||
|
delete [] data_;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Get a constant pointer to the underlying data. */
|
||||||
|
T * data() const
|
||||||
|
{
|
||||||
|
return data_;
|
||||||
|
}
|
||||||
|
|
||||||
|
T * end() const
|
||||||
|
{
|
||||||
|
return data_ + length_;
|
||||||
|
}
|
||||||
|
|
||||||
|
const T& at(size_t index) const
|
||||||
|
{
|
||||||
|
assert(index < length_ && "out of range");
|
||||||
|
return data_[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
T& at(size_t index)
|
||||||
|
{
|
||||||
|
assert(index < length_ && "out of range");
|
||||||
|
return data_[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Get how much underlying storage this auto_array has. */
|
||||||
|
size_t capacity() const
|
||||||
|
{
|
||||||
|
return capacity_;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Get how much elements this auto_array contains. */
|
||||||
|
size_t length() const
|
||||||
|
{
|
||||||
|
return length_;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Keeps the storage, but removes all the elements from the array. */
|
||||||
|
void clear()
|
||||||
|
{
|
||||||
|
length_ = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Change the storage of this auto array, copying the elements to the new
|
||||||
|
* storage.
|
||||||
|
* @returns true in case of success
|
||||||
|
* @returns false if the new capacity is not big enough to accomodate for the
|
||||||
|
* elements in the array.
|
||||||
|
*/
|
||||||
|
bool reserve(size_t new_capacity)
|
||||||
|
{
|
||||||
|
if (new_capacity < length_) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
T * new_data = new T[new_capacity];
|
||||||
|
if (data_ && length_) {
|
||||||
|
PodCopy(new_data, data_, length_);
|
||||||
|
}
|
||||||
|
capacity_ = new_capacity;
|
||||||
|
delete [] data_;
|
||||||
|
data_ = new_data;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Append `length` elements to the end of the array, resizing the array if
|
||||||
|
* needed.
|
||||||
|
* @parameter elements the elements to append to the array.
|
||||||
|
* @parameter length the number of elements to append to the array.
|
||||||
|
*/
|
||||||
|
void push(const T * elements, size_t length)
|
||||||
|
{
|
||||||
|
if (length_ + length > capacity_) {
|
||||||
|
reserve(length_ + length);
|
||||||
|
}
|
||||||
|
PodCopy(data_ + length_, elements, length);
|
||||||
|
length_ += length;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Append `length` zero-ed elements to the end of the array, resizing the
|
||||||
|
* array if needed.
|
||||||
|
* @parameter length the number of elements to append to the array.
|
||||||
|
*/
|
||||||
|
void push_silence(size_t length)
|
||||||
|
{
|
||||||
|
if (length_ + length > capacity_) {
|
||||||
|
reserve(length + length_);
|
||||||
|
}
|
||||||
|
PodZero(data_ + length_, length);
|
||||||
|
length_ += length;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Prepend `length` zero-ed elements to the end of the array, resizing the
|
||||||
|
* array if needed.
|
||||||
|
* @parameter length the number of elements to prepend to the array.
|
||||||
|
*/
|
||||||
|
void push_front_silence(size_t length)
|
||||||
|
{
|
||||||
|
if (length_ + length > capacity_) {
|
||||||
|
reserve(length + length_);
|
||||||
|
}
|
||||||
|
PodMove(data_ + length, data_, length_);
|
||||||
|
PodZero(data_, length);
|
||||||
|
length_ += length;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Return the number of free elements in the array. */
|
||||||
|
size_t available() const
|
||||||
|
{
|
||||||
|
return capacity_ - length_;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Copies `length` elements to `elements` if it is not null, and shift
|
||||||
|
* the remaining elements of the `auto_array` to the beginning.
|
||||||
|
* @parameter elements a buffer to copy the elements to, or nullptr.
|
||||||
|
* @parameter length the number of elements to copy.
|
||||||
|
* @returns true in case of success.
|
||||||
|
* @returns false if the auto_array contains less than `length` elements. */
|
||||||
|
bool pop(T * elements, size_t length)
|
||||||
|
{
|
||||||
|
if (length > length_) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (elements) {
|
||||||
|
PodCopy(elements, data_, length);
|
||||||
|
}
|
||||||
|
PodMove(data_, data_ + length, length_ - length);
|
||||||
|
|
||||||
|
length_ -= length;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void set_length(size_t length)
|
||||||
|
{
|
||||||
|
assert(length <= capacity_);
|
||||||
|
length_ = length;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
/** The underlying storage */
|
||||||
|
T * data_;
|
||||||
|
/** The size, in number of elements, of the storage. */
|
||||||
|
size_t capacity_;
|
||||||
|
/** The number of elements the array contains. */
|
||||||
|
size_t length_;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct auto_array_wrapper {
|
||||||
|
virtual void push(void * elements, size_t length) = 0;
|
||||||
|
virtual size_t length() = 0;
|
||||||
|
virtual void push_silence(size_t length) = 0;
|
||||||
|
virtual bool pop(size_t length) = 0;
|
||||||
|
virtual void * data() = 0;
|
||||||
|
virtual void * end() = 0;
|
||||||
|
virtual void clear() = 0;
|
||||||
|
virtual bool reserve(size_t capacity) = 0;
|
||||||
|
virtual void set_length(size_t length) = 0;
|
||||||
|
virtual ~auto_array_wrapper() {}
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
struct auto_array_wrapper_impl : public auto_array_wrapper {
|
||||||
|
auto_array_wrapper_impl() {}
|
||||||
|
|
||||||
|
explicit auto_array_wrapper_impl(uint32_t size)
|
||||||
|
: ar(size)
|
||||||
|
{}
|
||||||
|
|
||||||
|
void push(void * elements, size_t length) override {
|
||||||
|
ar.push(static_cast<T *>(elements), length);
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t length() override {
|
||||||
|
return ar.length();
|
||||||
|
}
|
||||||
|
|
||||||
|
void push_silence(size_t length) override {
|
||||||
|
ar.push_silence(length);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool pop(size_t length) override {
|
||||||
|
return ar.pop(nullptr, length);
|
||||||
|
}
|
||||||
|
|
||||||
|
void * data() override {
|
||||||
|
return ar.data();
|
||||||
|
}
|
||||||
|
|
||||||
|
void * end() override {
|
||||||
|
return ar.end();
|
||||||
|
}
|
||||||
|
|
||||||
|
void clear() override {
|
||||||
|
ar.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool reserve(size_t capacity) override {
|
||||||
|
return ar.reserve(capacity);
|
||||||
|
}
|
||||||
|
|
||||||
|
void set_length(size_t length) override {
|
||||||
|
ar.set_length(length);
|
||||||
|
}
|
||||||
|
|
||||||
|
~auto_array_wrapper_impl() {
|
||||||
|
ar.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
auto_array<T> ar;
|
||||||
|
};
|
||||||
|
|
||||||
|
using auto_lock = std::lock_guard<owned_critical_section>;
|
||||||
|
#endif // __cplusplus
|
||||||
|
|
||||||
|
// C language helpers
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
int cubeb_utils_default_device_collection_destroy(cubeb * context,
|
||||||
|
cubeb_device_collection * collection);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif /* CUBEB_UTILS */
|
|
@ -0,0 +1,89 @@
|
||||||
|
/*
|
||||||
|
* Copyright © 2016 Mozilla Foundation
|
||||||
|
*
|
||||||
|
* This program is made available under an ISC-style license. See the
|
||||||
|
* accompanying file LICENSE for details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#if !defined(CUBEB_UTILS_UNIX)
|
||||||
|
#define CUBEB_UTILS_UNIX
|
||||||
|
|
||||||
|
#include <pthread.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
/* This wraps a critical section to track the owner in debug mode. */
|
||||||
|
class owned_critical_section
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
owned_critical_section()
|
||||||
|
{
|
||||||
|
pthread_mutexattr_t attr;
|
||||||
|
pthread_mutexattr_init(&attr);
|
||||||
|
#ifndef NDEBUG
|
||||||
|
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_ERRORCHECK);
|
||||||
|
#else
|
||||||
|
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_NORMAL);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef NDEBUG
|
||||||
|
int r =
|
||||||
|
#endif
|
||||||
|
pthread_mutex_init(&mutex, &attr);
|
||||||
|
#ifndef NDEBUG
|
||||||
|
assert(r == 0);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
pthread_mutexattr_destroy(&attr);
|
||||||
|
}
|
||||||
|
|
||||||
|
~owned_critical_section()
|
||||||
|
{
|
||||||
|
#ifndef NDEBUG
|
||||||
|
int r =
|
||||||
|
#endif
|
||||||
|
pthread_mutex_destroy(&mutex);
|
||||||
|
#ifndef NDEBUG
|
||||||
|
assert(r == 0);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void lock()
|
||||||
|
{
|
||||||
|
#ifndef NDEBUG
|
||||||
|
int r =
|
||||||
|
#endif
|
||||||
|
pthread_mutex_lock(&mutex);
|
||||||
|
#ifndef NDEBUG
|
||||||
|
assert(r == 0 && "Deadlock");
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void unlock()
|
||||||
|
{
|
||||||
|
#ifndef NDEBUG
|
||||||
|
int r =
|
||||||
|
#endif
|
||||||
|
pthread_mutex_unlock(&mutex);
|
||||||
|
#ifndef NDEBUG
|
||||||
|
assert(r == 0 && "Unlocking unlocked mutex");
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void assert_current_thread_owns()
|
||||||
|
{
|
||||||
|
#ifndef NDEBUG
|
||||||
|
int r = pthread_mutex_lock(&mutex);
|
||||||
|
assert(r == EDEADLK);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
pthread_mutex_t mutex;
|
||||||
|
|
||||||
|
// Disallow copy and assignment because pthread_mutex_t cannot be copied.
|
||||||
|
owned_critical_section(const owned_critical_section&);
|
||||||
|
owned_critical_section& operator=(const owned_critical_section&);
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif /* CUBEB_UTILS_UNIX */
|
|
@ -0,0 +1,71 @@
|
||||||
|
/*
|
||||||
|
* Copyright © 2016 Mozilla Foundation
|
||||||
|
*
|
||||||
|
* This program is made available under an ISC-style license. See the
|
||||||
|
* accompanying file LICENSE for details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#if !defined(CUBEB_UTILS_WIN)
|
||||||
|
#define CUBEB_UTILS_WIN
|
||||||
|
|
||||||
|
#include <windows.h>
|
||||||
|
#include "cubeb-internal.h"
|
||||||
|
|
||||||
|
/* 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 */
|
||||||
|
class owned_critical_section
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
owned_critical_section()
|
||||||
|
#ifndef NDEBUG
|
||||||
|
: owner(0)
|
||||||
|
#endif
|
||||||
|
{
|
||||||
|
InitializeCriticalSection(&critical_section);
|
||||||
|
}
|
||||||
|
|
||||||
|
~owned_critical_section()
|
||||||
|
{
|
||||||
|
DeleteCriticalSection(&critical_section);
|
||||||
|
}
|
||||||
|
|
||||||
|
void lock()
|
||||||
|
{
|
||||||
|
EnterCriticalSection(&critical_section);
|
||||||
|
#ifndef NDEBUG
|
||||||
|
XASSERT(owner != GetCurrentThreadId() && "recursive locking");
|
||||||
|
owner = GetCurrentThreadId();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void unlock()
|
||||||
|
{
|
||||||
|
#ifndef NDEBUG
|
||||||
|
/* GetCurrentThreadId cannot return 0: it is not a the valid thread id */
|
||||||
|
owner = 0;
|
||||||
|
#endif
|
||||||
|
LeaveCriticalSection(&critical_section);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* This is guaranteed to have the good behaviour if it succeeds. The behaviour
|
||||||
|
is undefined otherwise. */
|
||||||
|
void assert_current_thread_owns()
|
||||||
|
{
|
||||||
|
#ifndef NDEBUG
|
||||||
|
/* This implies owner != 0, because GetCurrentThreadId cannot return 0. */
|
||||||
|
XASSERT(owner == GetCurrentThreadId());
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
CRITICAL_SECTION critical_section;
|
||||||
|
#ifndef NDEBUG
|
||||||
|
DWORD owner;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Disallow copy and assignment because CRICICAL_SECTION cannot be copied.
|
||||||
|
owned_critical_section(const owned_critical_section&);
|
||||||
|
owned_critical_section& operator=(const owned_critical_section&);
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif /* CUBEB_UTILS_WIN */
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -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
|
|
@ -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
|
File diff suppressed because it is too large
Load Diff
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -6,6 +6,8 @@ Dolphin includes or links code of the following third-party software projects:
|
||||||
[LGPLv2.1+](https://git.kernel.org/cgit/bluetooth/bluez.git/tree/COPYING.LIB)
|
[LGPLv2.1+](https://git.kernel.org/cgit/bluetooth/bluez.git/tree/COPYING.LIB)
|
||||||
- [Bochs](http://bochs.sourceforge.net/):
|
- [Bochs](http://bochs.sourceforge.net/):
|
||||||
[LGPLv2.1+](http://bochs.sourceforge.net/cgi-bin/lxr/source/COPYING)
|
[LGPLv2.1+](http://bochs.sourceforge.net/cgi-bin/lxr/source/COPYING)
|
||||||
|
- [cubeb](https://github.com/kinetiknz/cubeb):
|
||||||
|
[ISC](https://github.com/kinetiknz/cubeb/blob/master/LICENSE)
|
||||||
- [ENet](http://enet.bespin.org/):
|
- [ENet](http://enet.bespin.org/):
|
||||||
[MIT](http://enet.bespin.org/License.html)
|
[MIT](http://enet.bespin.org/License.html)
|
||||||
- [GCEmu](http://sourceforge.net/projects/gcemu-project/):
|
- [GCEmu](http://sourceforge.net/projects/gcemu-project/):
|
||||||
|
|
Loading…
Reference in New Issue