mirror of
https://github.com/RetroDECK/Duckstation.git
synced 2024-11-21 21:35:38 +00:00
dep/cubeb: Minimize and update to 54217bc
This commit is contained in:
parent
7cc52bba23
commit
1b948aab62
|
@ -2,10 +2,8 @@
|
|||
# - backend selection via command line, rather than simply detecting headers.
|
||||
|
||||
cmake_minimum_required(VERSION 3.14 FATAL_ERROR)
|
||||
project(cubeb
|
||||
VERSION 0.0.0)
|
||||
project(cubeb C CXX)
|
||||
|
||||
option(BUILD_RUST_LIBS "Build rust backends" OFF)
|
||||
option(LAZY_LOAD_LIBS "Lazily load shared libraries" ON)
|
||||
|
||||
if(NOT CMAKE_BUILD_TYPE)
|
||||
|
@ -14,25 +12,17 @@ if(NOT CMAKE_BUILD_TYPE)
|
|||
endif()
|
||||
|
||||
set(CMAKE_C_STANDARD 99)
|
||||
set(CMAKE_CXX_STANDARD 11)
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
|
||||
if (BUILD_RUST_LIBS)
|
||||
if(EXISTS "${PROJECT_SOURCE_DIR}/src/cubeb-pulse-rs")
|
||||
set(USE_PULSE_RUST 1)
|
||||
endif()
|
||||
if(EXISTS "${PROJECT_SOURCE_DIR}/src/cubeb-coreaudio-rs")
|
||||
set(USE_AUDIOUNIT_RUST 1)
|
||||
endif()
|
||||
endif()
|
||||
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
||||
|
||||
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 -fno-exceptions -fno-rtti")
|
||||
else()
|
||||
string(REPLACE "/GR" "" CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS}) # Disable RTTI
|
||||
string(REPLACE "/EHsc" "" CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS}) # Disable Exceptions
|
||||
string(REPLACE "/GR" "" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}") # Disable RTTI
|
||||
string(REPLACE "/EHsc" "" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}") # Disable Exceptions
|
||||
endif()
|
||||
|
||||
add_library(cubeb
|
||||
|
@ -46,43 +36,15 @@ add_library(cubeb
|
|||
target_include_directories(cubeb
|
||||
PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include> $<INSTALL_INTERFACE:include>
|
||||
)
|
||||
set_target_properties(cubeb PROPERTIES
|
||||
VERSION ${cubeb_VERSION}
|
||||
SOVERSION ${cubeb_VERSION_MAJOR}
|
||||
)
|
||||
|
||||
include(CMakePackageConfigHelpers)
|
||||
write_basic_package_version_file(
|
||||
"${PROJECT_BINARY_DIR}/${PROJECT_NAME}ConfigVersion.cmake"
|
||||
COMPATIBILITY SameMajorVersion
|
||||
)
|
||||
|
||||
configure_package_config_file(
|
||||
"Config.cmake.in"
|
||||
"${PROJECT_BINARY_DIR}/${PROJECT_NAME}Config.cmake"
|
||||
INSTALL_DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}"
|
||||
)
|
||||
|
||||
install(
|
||||
FILES "${PROJECT_BINARY_DIR}/${PROJECT_NAME}Config.cmake" "${PROJECT_BINARY_DIR}/${PROJECT_NAME}ConfigVersion.cmake"
|
||||
DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}"
|
||||
)
|
||||
|
||||
install(TARGETS cubeb EXPORT "${PROJECT_NAME}Targets")
|
||||
install(
|
||||
EXPORT "${PROJECT_NAME}Targets"
|
||||
NAMESPACE "${PROJECT_NAME}::"
|
||||
DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}"
|
||||
)
|
||||
|
||||
add_library(speex OBJECT subprojects/speex/resample.c)
|
||||
set_target_properties(speex PROPERTIES POSITION_INDEPENDENT_CODE TRUE)
|
||||
target_include_directories(speex INTERFACE subprojects)
|
||||
target_compile_definitions(speex PUBLIC
|
||||
OUTSIDE_SPEEX
|
||||
FLOATING_POINT
|
||||
EXPORT=
|
||||
RANDOM_PREFIX=speex
|
||||
OUTSIDE_SPEEX
|
||||
FLOATING_POINT
|
||||
EXPORT=
|
||||
RANDOM_PREFIX=speex
|
||||
)
|
||||
|
||||
# $<BUILD_INTERFACE:> required because of https://gitlab.kitware.com/cmake/cmake/-/issues/15415
|
||||
|
@ -96,17 +58,19 @@ find_package(Threads)
|
|||
target_link_libraries(cubeb PRIVATE Threads::Threads)
|
||||
|
||||
if(LAZY_LOAD_LIBS)
|
||||
check_include_files(pulse/pulseaudio.h USE_PULSE)
|
||||
check_include_files(alsa/asoundlib.h USE_ALSA)
|
||||
check_include_files(jack/jack.h USE_JACK)
|
||||
check_include_files(sndio.h USE_SNDIO)
|
||||
check_include_files(aaudio/AAudio.h USE_AAUDIO)
|
||||
if(NOT APPLE AND NOT WIN32)
|
||||
# Skip checks on MacOS because it takes ages in XCode.
|
||||
check_include_files(pulse/pulseaudio.h USE_PULSE)
|
||||
check_include_files(alsa/asoundlib.h USE_ALSA)
|
||||
check_include_files(jack/jack.h USE_JACK)
|
||||
check_include_files(sndio.h USE_SNDIO)
|
||||
|
||||
if(USE_PULSE OR USE_ALSA OR USE_JACK OR USE_SNDIO OR USE_AAUDIO)
|
||||
target_link_libraries(cubeb PRIVATE ${CMAKE_DL_LIBS})
|
||||
if(USE_PULSE OR USE_ALSA OR USE_JACK OR USE_SNDIO)
|
||||
target_link_libraries(cubeb PRIVATE ${CMAKE_DL_LIBS})
|
||||
endif()
|
||||
endif()
|
||||
|
||||
else()
|
||||
elseif(NOT APPLE AND NOT WIN32)
|
||||
|
||||
find_package(PkgConfig REQUIRED)
|
||||
|
||||
|
@ -136,12 +100,6 @@ else()
|
|||
target_compile_definitions(cubeb PRIVATE DISABLE_LIBSNDIO_DLOPEN)
|
||||
target_link_libraries(cubeb PRIVATE sndio)
|
||||
endif()
|
||||
|
||||
check_include_files(aaudio/AAudio.h USE_AAUDIO)
|
||||
if(USE_AAUDIO)
|
||||
target_compile_definitions(cubeb PRIVATE DISABLE_LIBAAUDIO_DLOPEN)
|
||||
target_link_libraries(cubeb PRIVATE aaudio)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(USE_PULSE)
|
||||
|
@ -164,138 +122,57 @@ if(USE_SNDIO)
|
|||
target_compile_definitions(cubeb PRIVATE USE_SNDIO)
|
||||
endif()
|
||||
|
||||
if(USE_AAUDIO)
|
||||
target_sources(cubeb PRIVATE src/cubeb_aaudio.cpp)
|
||||
target_compile_definitions(cubeb PRIVATE USE_AAUDIO)
|
||||
|
||||
# set this definition to enable low latency mode. Possibly bad for battery
|
||||
target_compile_definitions(cubeb PRIVATE CUBEB_AAUDIO_LOW_LATENCY)
|
||||
|
||||
# set this definition to enable power saving mode. Possibly resulting
|
||||
# in high latency
|
||||
# target_compile_definitions(cubeb PRIVATE CUBEB_AAUDIO_LOW_POWER_SAVING)
|
||||
|
||||
# set this mode to make the backend use an exclusive stream.
|
||||
# will decrease latency.
|
||||
# target_compile_definitions(cubeb PRIVATE CUBEB_AAUDIO_EXCLUSIVE_STREAM)
|
||||
endif()
|
||||
|
||||
check_include_files(AudioUnit/AudioUnit.h USE_AUDIOUNIT)
|
||||
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(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 ole32 ksuser)
|
||||
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
|
||||
src/cubeb-jni.cpp)
|
||||
target_compile_definitions(cubeb PRIVATE USE_OPENSL)
|
||||
target_link_libraries(cubeb PRIVATE OpenSLES)
|
||||
endif()
|
||||
|
||||
check_include_files(sys/soundcard.h HAVE_SYS_SOUNDCARD_H)
|
||||
if(HAVE_SYS_SOUNDCARD_H)
|
||||
try_compile(USE_OSS "${PROJECT_BINARY_DIR}/compile_tests"
|
||||
${PROJECT_SOURCE_DIR}/cmake/compile_tests/oss_is_v4.c)
|
||||
if(USE_OSS)
|
||||
# strlcpy is not available on BSD systems that use glibc,
|
||||
# like Debian kfreebsd, so try using libbsd if available
|
||||
include(CheckSymbolExists)
|
||||
check_symbol_exists(strlcpy string.h HAVE_STRLCPY)
|
||||
if(NOT HAVE_STRLCPY)
|
||||
pkg_check_modules(libbsd-overlay IMPORTED_TARGET libbsd-overlay)
|
||||
if(libbsd-overlay_FOUND)
|
||||
target_link_libraries(cubeb PRIVATE PkgConfig::libbsd-overlay)
|
||||
set(HAVE_STRLCPY true)
|
||||
endif()
|
||||
endif()
|
||||
if (HAVE_STRLCPY)
|
||||
target_sources(cubeb PRIVATE
|
||||
src/cubeb_oss.c)
|
||||
target_compile_definitions(cubeb PRIVATE USE_OSS)
|
||||
endif()
|
||||
if(APPLE)
|
||||
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()
|
||||
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)
|
||||
if(WIN32)
|
||||
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 ole32 ksuser)
|
||||
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()
|
||||
endif()
|
||||
|
||||
check_include_files(sys/audioio.h USE_SUN)
|
||||
if(USE_SUN)
|
||||
target_sources(cubeb PRIVATE
|
||||
src/cubeb_sun.c)
|
||||
target_compile_definitions(cubeb PRIVATE USE_SUN)
|
||||
if(NOT WIN32 AND NOT APPLE)
|
||||
check_include_files(sys/soundcard.h HAVE_SYS_SOUNDCARD_H)
|
||||
if(HAVE_SYS_SOUNDCARD_H)
|
||||
try_compile(USE_OSS "${PROJECT_BINARY_DIR}/compile_tests"
|
||||
${PROJECT_SOURCE_DIR}/cmake/compile_tests/oss_is_v4.c)
|
||||
if(USE_OSS)
|
||||
# strlcpy is not available on BSD systems that use glibc,
|
||||
# like Debian kfreebsd, so try using libbsd if available
|
||||
include(CheckSymbolExists)
|
||||
check_symbol_exists(strlcpy string.h HAVE_STRLCPY)
|
||||
if(NOT HAVE_STRLCPY)
|
||||
pkg_check_modules(libbsd-overlay IMPORTED_TARGET libbsd-overlay)
|
||||
if(libbsd-overlay_FOUND)
|
||||
target_link_libraries(cubeb PRIVATE PkgConfig::libbsd-overlay)
|
||||
set(HAVE_STRLCPY true)
|
||||
endif()
|
||||
endif()
|
||||
if (HAVE_STRLCPY)
|
||||
target_sources(cubeb PRIVATE
|
||||
src/cubeb_oss.c)
|
||||
target_compile_definitions(cubeb PRIVATE USE_OSS)
|
||||
endif()
|
||||
endif()
|
||||
endif()
|
||||
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()
|
||||
|
||||
if(USE_PULSE AND USE_PULSE_RUST)
|
||||
include(ExternalProject)
|
||||
set_directory_properties(PROPERTIES EP_PREFIX ${CMAKE_BINARY_DIR}/rust)
|
||||
ExternalProject_Add(
|
||||
cubeb_pulse_rs
|
||||
DOWNLOAD_COMMAND ""
|
||||
CONFIGURE_COMMAND ""
|
||||
BUILD_COMMAND cargo build COMMAND cargo build --release
|
||||
BUILD_ALWAYS ON
|
||||
BINARY_DIR "${PROJECT_SOURCE_DIR}/src/cubeb-pulse-rs"
|
||||
INSTALL_COMMAND ""
|
||||
LOG_BUILD ON)
|
||||
add_dependencies(cubeb cubeb_pulse_rs)
|
||||
target_compile_definitions(cubeb PRIVATE USE_PULSE_RUST)
|
||||
target_link_libraries(cubeb PRIVATE
|
||||
debug "${PROJECT_SOURCE_DIR}/src/cubeb-pulse-rs/target/debug/libcubeb_pulse.a"
|
||||
optimized "${PROJECT_SOURCE_DIR}/src/cubeb-pulse-rs/target/release/libcubeb_pulse.a" pulse)
|
||||
endif()
|
||||
|
||||
if(USE_AUDIOUNIT AND USE_AUDIOUNIT_RUST)
|
||||
include(ExternalProject)
|
||||
set_directory_properties(PROPERTIES EP_PREFIX ${CMAKE_BINARY_DIR}/rust)
|
||||
ExternalProject_Add(
|
||||
cubeb_coreaudio_rs
|
||||
DOWNLOAD_COMMAND ""
|
||||
CONFIGURE_COMMAND ""
|
||||
BUILD_COMMAND cargo build COMMAND cargo build --release
|
||||
BUILD_ALWAYS ON
|
||||
BINARY_DIR "${PROJECT_SOURCE_DIR}/src/cubeb-coreaudio-rs"
|
||||
INSTALL_COMMAND ""
|
||||
LOG_BUILD ON)
|
||||
add_dependencies(cubeb cubeb_coreaudio_rs)
|
||||
target_compile_definitions(cubeb PRIVATE USE_AUDIOUNIT_RUST)
|
||||
target_link_libraries(cubeb PRIVATE
|
||||
debug "${PROJECT_SOURCE_DIR}/src/cubeb-coreaudio-rs/target/debug/libcubeb_coreaudio.a"
|
||||
optimized "${PROJECT_SOURCE_DIR}/src/cubeb-coreaudio-rs/target/release/libcubeb_coreaudio.a")
|
||||
endif()
|
||||
|
||||
|
|
|
@ -1,4 +0,0 @@
|
|||
@PACKAGE_INIT@
|
||||
|
||||
include("${CMAKE_CURRENT_LIST_DIR}/cubebTargets.cmake")
|
||||
check_required_components(cubeb)
|
|
@ -1,14 +0,0 @@
|
|||
SET(CMAKE_SYSTEM_NAME Windows)
|
||||
|
||||
set(COMPILER_PREFIX "i686-w64-mingw32")
|
||||
|
||||
find_program(CMAKE_RC_COMPILER NAMES ${COMPILER_PREFIX}-windres)
|
||||
find_program(CMAKE_C_COMPILER NAMES ${COMPILER_PREFIX}-gcc-posix)
|
||||
find_program(CMAKE_CXX_COMPILER NAMES ${COMPILER_PREFIX}-g++-posix)
|
||||
|
||||
SET(CMAKE_FIND_ROOT_PATH /usr/${COMPILER_PREFIX})
|
||||
|
||||
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
|
||||
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
|
||||
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
|
||||
|
|
@ -1,36 +0,0 @@
|
|||
{
|
||||
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
|
||||
}
|
||||
|
|
@ -6,7 +6,6 @@
|
|||
<ClInclude Include="include\cubeb\cubeb_export.h" />
|
||||
<ClInclude Include="src\cubeb-internal.h" />
|
||||
<ClInclude Include="src\cubeb-speex-resampler.h" />
|
||||
<ClInclude Include="src\cubeb_array_queue.h" />
|
||||
<ClInclude Include="src\cubeb_assert.h" />
|
||||
<ClInclude Include="src\cubeb_log.h" />
|
||||
<ClInclude Include="src\cubeb_mixer.h" />
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
<ClInclude Include="include\cubeb\cubeb_export.h" />
|
||||
<ClInclude Include="src\cubeb-internal.h" />
|
||||
<ClInclude Include="src\cubeb-speex-resampler.h" />
|
||||
<ClInclude Include="src\cubeb_array_queue.h" />
|
||||
<ClInclude Include="src\cubeb_assert.h" />
|
||||
<ClInclude Include="src\cubeb_log.h" />
|
||||
<ClInclude Include="src\cubeb_mixer.h" />
|
||||
|
|
|
@ -163,6 +163,7 @@ typedef enum {
|
|||
implications. */
|
||||
} cubeb_log_level;
|
||||
|
||||
/// A single channel position, to be used in a bitmask.
|
||||
typedef enum {
|
||||
CHANNEL_UNKNOWN = 0,
|
||||
CHANNEL_FRONT_LEFT = 1 << 0,
|
||||
|
@ -185,6 +186,9 @@ typedef enum {
|
|||
CHANNEL_TOP_BACK_RIGHT = 1 << 17
|
||||
} cubeb_channel;
|
||||
|
||||
/// A bitmask representing the channel layout of a cubeb stream. This is
|
||||
/// bit-compatible with WAVEFORMATEXENSIBLE and in the same order as the SMPTE
|
||||
/// ordering.
|
||||
typedef uint32_t cubeb_channel_layout;
|
||||
// Some common layout definitions.
|
||||
enum {
|
||||
|
@ -433,7 +437,7 @@ typedef void (*cubeb_state_callback)(cubeb_stream * stream, void * user_ptr,
|
|||
|
||||
/**
|
||||
* User supplied callback called when the underlying device changed.
|
||||
* @param user The pointer passed to cubeb_stream_init. */
|
||||
* @param user_ptr The pointer passed to cubeb_stream_init. */
|
||||
typedef void (*cubeb_device_changed_callback)(void * user_ptr);
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,85 +0,0 @@
|
|||
/*
|
||||
* 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;
|
|
@ -1,76 +0,0 @@
|
|||
#ifndef _CUBEB_OUTPUT_LATENCY_H_
|
||||
#define _CUBEB_OUTPUT_LATENCY_H_
|
||||
|
||||
#include "../cubeb-jni.h"
|
||||
#include "cubeb_media_library.h"
|
||||
#include <stdbool.h>
|
||||
|
||||
struct output_latency_function {
|
||||
media_lib * from_lib;
|
||||
cubeb_jni * from_jni;
|
||||
int version;
|
||||
};
|
||||
|
||||
typedef struct output_latency_function output_latency_function;
|
||||
|
||||
const int ANDROID_JELLY_BEAN_MR1_4_2 = 17;
|
||||
|
||||
output_latency_function *
|
||||
cubeb_output_latency_load_method(int version)
|
||||
{
|
||||
output_latency_function * ol = NULL;
|
||||
ol = calloc(1, sizeof(output_latency_function));
|
||||
|
||||
ol->version = version;
|
||||
|
||||
if (ol->version > ANDROID_JELLY_BEAN_MR1_4_2) {
|
||||
ol->from_jni = cubeb_jni_init();
|
||||
return ol;
|
||||
}
|
||||
|
||||
ol->from_lib = cubeb_load_media_library();
|
||||
return ol;
|
||||
}
|
||||
|
||||
bool
|
||||
cubeb_output_latency_method_is_loaded(output_latency_function * ol)
|
||||
{
|
||||
assert(ol);
|
||||
if (ol->version > ANDROID_JELLY_BEAN_MR1_4_2) {
|
||||
return !!ol->from_jni;
|
||||
}
|
||||
|
||||
return !!ol->from_lib;
|
||||
}
|
||||
|
||||
void
|
||||
cubeb_output_latency_unload_method(output_latency_function * ol)
|
||||
{
|
||||
if (!ol) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (ol->version > ANDROID_JELLY_BEAN_MR1_4_2 && ol->from_jni) {
|
||||
cubeb_jni_destroy(ol->from_jni);
|
||||
}
|
||||
|
||||
if (ol->version <= ANDROID_JELLY_BEAN_MR1_4_2 && ol->from_lib) {
|
||||
cubeb_close_media_library(ol->from_lib);
|
||||
}
|
||||
|
||||
free(ol);
|
||||
}
|
||||
|
||||
uint32_t
|
||||
cubeb_get_output_latency(output_latency_function * ol)
|
||||
{
|
||||
assert(cubeb_output_latency_method_is_loaded(ol));
|
||||
|
||||
if (ol->version > ANDROID_JELLY_BEAN_MR1_4_2) {
|
||||
return cubeb_get_output_latency_from_jni(ol->from_jni);
|
||||
}
|
||||
|
||||
return cubeb_get_output_latency_from_media_library(ol->from_lib);
|
||||
}
|
||||
|
||||
#endif // _CUBEB_OUTPUT_LATENCY_H_
|
|
@ -1,64 +0,0 @@
|
|||
#ifndef _CUBEB_MEDIA_LIBRARY_H_
|
||||
#define _CUBEB_MEDIA_LIBRARY_H_
|
||||
|
||||
struct media_lib {
|
||||
void * libmedia;
|
||||
int32_t (*get_output_latency)(uint32_t * latency, int stream_type);
|
||||
};
|
||||
|
||||
typedef struct media_lib media_lib;
|
||||
|
||||
media_lib *
|
||||
cubeb_load_media_library()
|
||||
{
|
||||
media_lib ml = {0};
|
||||
ml.libmedia = dlopen("libmedia.so", RTLD_LAZY);
|
||||
if (!ml.libmedia) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Get the latency, in ms, from AudioFlinger. First, try the most recent
|
||||
// signature. status_t AudioSystem::getOutputLatency(uint32_t* latency,
|
||||
// audio_stream_type_t streamType)
|
||||
ml.get_output_latency = dlsym(
|
||||
ml.libmedia,
|
||||
"_ZN7android11AudioSystem16getOutputLatencyEPj19audio_stream_type_t");
|
||||
if (!ml.get_output_latency) {
|
||||
// In case of failure, try the signature from legacy version.
|
||||
// status_t AudioSystem::getOutputLatency(uint32_t* latency, int streamType)
|
||||
ml.get_output_latency =
|
||||
dlsym(ml.libmedia, "_ZN7android11AudioSystem16getOutputLatencyEPji");
|
||||
if (!ml.get_output_latency) {
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
media_lib * rv = NULL;
|
||||
rv = calloc(1, sizeof(media_lib));
|
||||
assert(rv);
|
||||
*rv = ml;
|
||||
return rv;
|
||||
}
|
||||
|
||||
void
|
||||
cubeb_close_media_library(media_lib * ml)
|
||||
{
|
||||
dlclose(ml->libmedia);
|
||||
ml->libmedia = NULL;
|
||||
ml->get_output_latency = NULL;
|
||||
free(ml);
|
||||
}
|
||||
|
||||
uint32_t
|
||||
cubeb_get_output_latency_from_media_library(media_lib * ml)
|
||||
{
|
||||
uint32_t latency = 0;
|
||||
const int audio_stream_type_music = 3;
|
||||
int32_t r = ml->get_output_latency(&latency, audio_stream_type_music);
|
||||
if (r) {
|
||||
return 0;
|
||||
}
|
||||
return latency;
|
||||
}
|
||||
|
||||
#endif // _CUBEB_MEDIA_LIBRARY_H_
|
|
@ -1,104 +0,0 @@
|
|||
/*
|
||||
* 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)
|
||||
/** uses the main microphone unprocessed */
|
||||
#define SL_ANDROID_RECORDING_PRESET_UNPROCESSED ((SLuint32)0x00000005)
|
||||
|
||||
/*---------------------------------------------------------------------------*/
|
||||
/* 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)
|
||||
|
||||
/*---------------------------------------------------------------------------*/
|
||||
/* Android AudioPlayer and AudioRecorder configuration */
|
||||
/*---------------------------------------------------------------------------*/
|
||||
|
||||
/** Audio Performance mode.
|
||||
* Performance mode tells the framework how to configure the audio path
|
||||
* for a player or recorder according to application performance and
|
||||
* functional requirements.
|
||||
* It affects the output or input latency based on acceptable tradeoffs on
|
||||
* battery drain and use of pre or post processing effects.
|
||||
* Performance mode should be set before realizing the object and should be
|
||||
* read after realizing the object to check if the requested mode could be
|
||||
* granted or not.
|
||||
*/
|
||||
/** Audio Performance mode key */
|
||||
#define SL_ANDROID_KEY_PERFORMANCE_MODE \
|
||||
((const SLchar *)"androidPerformanceMode")
|
||||
|
||||
/** Audio performance values */
|
||||
/* No specific performance requirement. Allows HW and SW pre/post
|
||||
* processing. */
|
||||
#define SL_ANDROID_PERFORMANCE_NONE ((SLuint32)0x00000000)
|
||||
/* Priority given to latency. No HW or software pre/post processing.
|
||||
* This is the default if no performance mode is specified. */
|
||||
#define SL_ANDROID_PERFORMANCE_LATENCY ((SLuint32)0x00000001)
|
||||
/* Priority given to latency while still allowing HW pre and post
|
||||
* processing. */
|
||||
#define SL_ANDROID_PERFORMANCE_LATENCY_EFFECTS ((SLuint32)0x00000002)
|
||||
/* Priority given to power saving if latency is not a concern.
|
||||
* Allows HW and SW pre/post processing. */
|
||||
#define SL_ANDROID_PERFORMANCE_POWER_SAVING ((SLuint32)0x00000003)
|
||||
|
||||
#endif /* OPENSL_ES_ANDROIDCONFIGURATION_H_ */
|
|
@ -3,6 +3,10 @@
|
|||
|
||||
typedef struct cubeb_jni cubeb_jni;
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
cubeb_jni *
|
||||
cubeb_jni_init();
|
||||
int
|
||||
|
@ -10,4 +14,8 @@ cubeb_get_output_latency_from_jni(cubeb_jni * cubeb_jni_ptr);
|
|||
void
|
||||
cubeb_jni_destroy(cubeb_jni * cubeb_jni_ptr);
|
||||
|
||||
#ifdef __cplusplus
|
||||
};
|
||||
#endif
|
||||
|
||||
#endif // _CUBEB_JNI_H_
|
||||
|
|
|
@ -1,38 +0,0 @@
|
|||
/*
|
||||
* 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
|
|
@ -31,10 +31,6 @@ struct cubeb_stream {
|
|||
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);
|
||||
|
@ -47,10 +43,6 @@ alsa_init(cubeb ** context, char const * context_name);
|
|||
int
|
||||
audiounit_init(cubeb ** context, char const * context_name);
|
||||
#endif
|
||||
#if defined(USE_AUDIOUNIT_RUST)
|
||||
int
|
||||
audiounit_rust_init(cubeb ** contet, char const * context_name);
|
||||
#endif
|
||||
#if defined(USE_WINMM)
|
||||
int
|
||||
winmm_init(cubeb ** context, char const * context_name);
|
||||
|
@ -63,30 +55,10 @@ wasapi_init(cubeb ** context, char const * context_name);
|
|||
int
|
||||
sndio_init(cubeb ** context, char const * context_name);
|
||||
#endif
|
||||
#if defined(USE_SUN)
|
||||
int
|
||||
sun_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_OSS)
|
||||
int
|
||||
oss_init(cubeb ** context, char const * context_name);
|
||||
#endif
|
||||
#if defined(USE_AAUDIO)
|
||||
int
|
||||
aaudio_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,
|
||||
|
@ -151,10 +123,6 @@ cubeb_init(cubeb ** context, char const * context_name,
|
|||
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)
|
||||
|
@ -167,10 +135,6 @@ cubeb_init(cubeb ** context, char const * context_name,
|
|||
} else if (!strcmp(backend_name, "audiounit")) {
|
||||
#if defined(USE_AUDIOUNIT)
|
||||
init_oneshot = audiounit_init;
|
||||
#endif
|
||||
} else if (!strcmp(backend_name, "audiounit-rust")) {
|
||||
#if defined(USE_AUDIOUNIT_RUST)
|
||||
init_oneshot = audiounit_rust_init;
|
||||
#endif
|
||||
} else if (!strcmp(backend_name, "wasapi")) {
|
||||
#if defined(USE_WASAPI)
|
||||
|
@ -183,30 +147,10 @@ cubeb_init(cubeb ** context, char const * context_name,
|
|||
} else if (!strcmp(backend_name, "sndio")) {
|
||||
#if defined(USE_SNDIO)
|
||||
init_oneshot = sndio_init;
|
||||
#endif
|
||||
} else if (!strcmp(backend_name, "sun")) {
|
||||
#if defined(USE_SUN)
|
||||
init_oneshot = sun_init;
|
||||
#endif
|
||||
} else if (!strcmp(backend_name, "opensl")) {
|
||||
#if defined(USE_OPENSL)
|
||||
init_oneshot = opensl_init;
|
||||
#endif
|
||||
} else if (!strcmp(backend_name, "oss")) {
|
||||
#if defined(USE_OSS)
|
||||
init_oneshot = oss_init;
|
||||
#endif
|
||||
} else if (!strcmp(backend_name, "aaudio")) {
|
||||
#if defined(USE_AAUDIO)
|
||||
init_oneshot = aaudio_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 */
|
||||
|
@ -219,9 +163,6 @@ cubeb_init(cubeb ** context, char const * context_name,
|
|||
* to override all other choices
|
||||
*/
|
||||
init_oneshot,
|
||||
#if defined(USE_PULSE_RUST)
|
||||
pulse_rust_init,
|
||||
#endif
|
||||
#if defined(USE_PULSE)
|
||||
pulse_init,
|
||||
#endif
|
||||
|
@ -237,9 +178,6 @@ cubeb_init(cubeb ** context, char const * context_name,
|
|||
#if defined(USE_OSS)
|
||||
oss_init,
|
||||
#endif
|
||||
#if defined(USE_AUDIOUNIT_RUST)
|
||||
audiounit_rust_init,
|
||||
#endif
|
||||
#if defined(USE_AUDIOUNIT)
|
||||
audiounit_init,
|
||||
#endif
|
||||
|
@ -251,20 +189,6 @@ cubeb_init(cubeb ** context, char const * context_name,
|
|||
#endif
|
||||
#if defined(USE_SUN)
|
||||
sun_init,
|
||||
#endif
|
||||
#if defined(USE_OPENSL)
|
||||
opensl_init,
|
||||
#endif
|
||||
// TODO: should probably be preferred over OpenSLES when available.
|
||||
// Initialization will fail on old android devices.
|
||||
#if defined(USE_AAUDIO)
|
||||
aaudio_init,
|
||||
#endif
|
||||
#if defined(USE_AUDIOTRACK)
|
||||
audiotrack_init,
|
||||
#endif
|
||||
#if defined(USE_KAI)
|
||||
kai_init,
|
||||
#endif
|
||||
};
|
||||
int i;
|
||||
|
@ -297,9 +221,6 @@ cubeb_get_backend_names()
|
|||
#if defined(USE_PULSE)
|
||||
"pulse",
|
||||
#endif
|
||||
#if defined(USE_PULSE_RUST)
|
||||
"pulse-rust",
|
||||
#endif
|
||||
#if defined(USE_JACK)
|
||||
"jack",
|
||||
#endif
|
||||
|
@ -309,9 +230,6 @@ cubeb_get_backend_names()
|
|||
#if defined(USE_AUDIOUNIT)
|
||||
"audiounit",
|
||||
#endif
|
||||
#if defined(USE_AUDIOUNIT_RUST)
|
||||
"audiounit-rust",
|
||||
#endif
|
||||
#if defined(USE_WASAPI)
|
||||
"wasapi",
|
||||
#endif
|
||||
|
@ -324,20 +242,8 @@ cubeb_get_backend_names()
|
|||
#if defined(USE_SUN)
|
||||
"sun",
|
||||
#endif
|
||||
#if defined(USE_OPENSL)
|
||||
"opensl",
|
||||
#endif
|
||||
#if defined(USE_OSS)
|
||||
"oss",
|
||||
#endif
|
||||
#if defined(USE_AAUDIO)
|
||||
"aaudio",
|
||||
#endif
|
||||
#if defined(USE_AUDIOTRACK)
|
||||
"audiotrack",
|
||||
#endif
|
||||
#if defined(USE_KAI)
|
||||
"kai",
|
||||
#endif
|
||||
NULL,
|
||||
};
|
||||
|
@ -406,6 +312,8 @@ cubeb_destroy(cubeb * context)
|
|||
}
|
||||
|
||||
context->ops->destroy(context);
|
||||
|
||||
cubeb_set_log_callback(CUBEB_LOG_DISABLED, NULL);
|
||||
}
|
||||
|
||||
int
|
||||
|
@ -687,14 +595,14 @@ cubeb_enumerate_devices(cubeb * context, cubeb_device_type devtype,
|
|||
int rv;
|
||||
if ((devtype & (CUBEB_DEVICE_TYPE_INPUT | CUBEB_DEVICE_TYPE_OUTPUT)) == 0)
|
||||
return CUBEB_ERROR_INVALID_PARAMETER;
|
||||
if (collection == NULL)
|
||||
if (context == NULL || 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) {
|
||||
if (cubeb_log_get_callback()) {
|
||||
for (size_t i = 0; i < collection->count; i++) {
|
||||
log_device(&collection->device[i]);
|
||||
}
|
||||
|
@ -756,21 +664,11 @@ cubeb_set_log_callback(cubeb_log_level log_level,
|
|||
return CUBEB_ERROR_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
if (g_cubeb_log_callback && log_callback) {
|
||||
if (cubeb_log_get_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");
|
||||
}
|
||||
cubeb_log_set(log_level, log_callback);
|
||||
|
||||
return CUBEB_OK;
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -7,9 +7,13 @@
|
|||
#undef NDEBUG
|
||||
#define _DEFAULT_SOURCE
|
||||
#define _BSD_SOURCE
|
||||
#if defined(__NetBSD__)
|
||||
#define _NETBSD_SOURCE /* timersub() */
|
||||
#endif
|
||||
#define _XOPEN_SOURCE 500
|
||||
#include "cubeb-internal.h"
|
||||
#include "cubeb/cubeb.h"
|
||||
#include "cubeb_tracing.h"
|
||||
#include <alsa/asoundlib.h>
|
||||
#include <assert.h>
|
||||
#include <dlfcn.h>
|
||||
|
@ -579,10 +583,14 @@ alsa_run_thread(void * context)
|
|||
cubeb * ctx = context;
|
||||
int r;
|
||||
|
||||
CUBEB_REGISTER_THREAD("cubeb rendering thread");
|
||||
|
||||
do {
|
||||
r = alsa_run(ctx);
|
||||
} while (r >= 0);
|
||||
|
||||
CUBEB_UNREGISTER_THREAD();
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
@ -957,11 +965,11 @@ alsa_destroy(cubeb * ctx)
|
|||
WRAP(snd_config_delete)(ctx->local_config);
|
||||
pthread_mutex_unlock(&cubeb_alsa_mutex);
|
||||
}
|
||||
|
||||
#ifndef DISABLE_LIBASOUND_DLOPEN
|
||||
if (ctx->libasound) {
|
||||
dlclose(ctx->libasound);
|
||||
}
|
||||
|
||||
#endif
|
||||
free(ctx);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,17 +0,0 @@
|
|||
#ifndef CUBEB_ANDROID_H
|
||||
#define CUBEB_ANDROID_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
// If the latency requested is above this threshold, this stream is considered
|
||||
// intended for playback (vs. real-time). Tell Android it should favor saving
|
||||
// power over performance or latency.
|
||||
// This is around 100ms at 44100 or 48000
|
||||
const uint16_t POWERSAVE_LATENCY_FRAMES_THRESHOLD = 4000;
|
||||
|
||||
#ifdef __cplusplus
|
||||
};
|
||||
#endif
|
||||
|
||||
#endif // CUBEB_ANDROID_H
|
|
@ -1,99 +0,0 @@
|
|||
/*
|
||||
* 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
|
|
@ -1,472 +0,0 @@
|
|||
/*
|
||||
* 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 <android/log.h>
|
||||
#include <assert.h>
|
||||
#include <dlfcn.h>
|
||||
#include <pthread.h>
|
||||
#include <stdlib.h>
|
||||
#include <time.h>
|
||||
|
||||
#include "android/audiotrack_definitions.h"
|
||||
#include "cubeb-internal.h"
|
||||
#include "cubeb/cubeb.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 {
|
||||
/* Note: Must match cubeb_stream layout in cubeb.c. */
|
||||
cubeb * context;
|
||||
void * user_ptr;
|
||||
/**/
|
||||
cubeb_stream_params params;
|
||||
cubeb_data_callback data_callback;
|
||||
cubeb_state_callback state_callback;
|
||||
void * instance;
|
||||
/* 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, AUDIO_STREAM_TYPE_MUSIC, params->rate);
|
||||
} else {
|
||||
status = ctx->klass.get_min_frame_count_gingerbread(
|
||||
min_frame_count, AUDIO_STREAM_TYPE_MUSIC, 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, AUDIO_STREAM_TYPE_MUSIC, 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,
|
||||
.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_get_input_latency = NULL,
|
||||
.stream_set_volume = audiotrack_stream_set_volume,
|
||||
.stream_set_name = NULL,
|
||||
.stream_get_current_device = NULL,
|
||||
.stream_device_destroy = NULL,
|
||||
.stream_register_device_changed_callback = NULL,
|
||||
.register_device_collection_changed = NULL};
|
|
@ -8,7 +8,7 @@
|
|||
*/
|
||||
#define _DEFAULT_SOURCE
|
||||
#define _BSD_SOURCE
|
||||
#ifndef __FreeBSD__
|
||||
#if !defined(__FreeBSD__) && !defined(__NetBSD__)
|
||||
#define _POSIX_SOURCE
|
||||
#endif
|
||||
#include "cubeb-internal.h"
|
||||
|
@ -788,10 +788,10 @@ cbjack_destroy(cubeb * context)
|
|||
|
||||
if (context->jack_client != NULL)
|
||||
WRAP(jack_client_close)(context->jack_client);
|
||||
|
||||
#ifndef DISABLE_LIBJACK_DLOPEN
|
||||
if (context->libjack)
|
||||
dlclose(context->libjack);
|
||||
|
||||
#endif
|
||||
free(context);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,369 +0,0 @@
|
|||
/*
|
||||
* Copyright © 2015 Mozilla Foundation
|
||||
*
|
||||
* This program is made available under an ISC-style license. See the
|
||||
* accompanying file LICENSE for details.
|
||||
*/
|
||||
#include <math.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <sys/fmutex.h>
|
||||
|
||||
#include <kai.h>
|
||||
|
||||
#include "cubeb-internal.h"
|
||||
#include "cubeb/cubeb.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 {
|
||||
/* Note: Must match cubeb_stream layout in cubeb.c. */
|
||||
cubeb * context;
|
||||
void * user_ptr;
|
||||
/**/
|
||||
cubeb_stream_params params;
|
||||
cubeb_data_callback data_callback;
|
||||
cubeb_state_callback state_callback;
|
||||
|
||||
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;
|
||||
|
||||
// Loopback is unsupported
|
||||
if (output_stream_params->prefs & CUBEB_STREAM_PREF_LOOPBACK) {
|
||||
return CUBEB_ERROR_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
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_get_input_latency = */ NULL,
|
||||
/*.stream_set_volume =*/kai_stream_set_volume,
|
||||
/*.stream_set_name =*/NULL,
|
||||
/*.stream_get_current_device =*/NULL,
|
||||
/*.stream_device_destroy =*/NULL,
|
||||
/*.stream_register_device_changed_callback=*/NULL,
|
||||
/*.register_device_collection_changed=*/NULL};
|
|
@ -16,8 +16,8 @@
|
|||
#include <time.h>
|
||||
#endif
|
||||
|
||||
cubeb_log_level g_cubeb_log_level;
|
||||
cubeb_log_callback g_cubeb_log_callback;
|
||||
static std::atomic<cubeb_log_level> g_cubeb_log_level;
|
||||
static std::atomic<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;
|
||||
|
@ -25,111 +25,56 @@ const size_t CUBEB_LOG_MESSAGE_MAX_SIZE = 256;
|
|||
* 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]() {
|
||||
CUBEB_REGISTER_THREAD("cubeb_log");
|
||||
while (true) {
|
||||
cubeb_log_message msg;
|
||||
while (msg_queue.dequeue(&msg, 1)) {
|
||||
LOG_INTERNAL_NO_FORMAT(CUBEB_LOG_NORMAL, "%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
|
||||
}
|
||||
CUBEB_UNREGISTER_THREAD();
|
||||
}).detach();
|
||||
}
|
||||
// Tell the underlying queue the producer thread has changed, so it does not
|
||||
// assert in debug. This should be called with the thread stopped.
|
||||
void reset_producer_thread() { msg_queue.reset_thread_ids(); }
|
||||
|
||||
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;
|
||||
};
|
||||
const size_t CUBEB_LOG_BATCH_PRINT_INTERVAL_MS = 10;
|
||||
|
||||
void
|
||||
cubeb_async_log(char const * fmt, ...)
|
||||
cubeb_noop_log_callback(char const * /* fmt */, ...)
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
cubeb_log_internal(char const * file, uint32_t line, 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);
|
||||
g_cubeb_log_callback.load()("%s:%d:%s", file, line, msg);
|
||||
}
|
||||
|
||||
void
|
||||
cubeb_async_log_reset_threads(void)
|
||||
cubeb_log_internal_no_format(const char * msg)
|
||||
{
|
||||
if (!g_cubeb_log_callback) {
|
||||
return;
|
||||
}
|
||||
cubeb_async_logger::get().reset_producer_thread();
|
||||
g_cubeb_log_callback.load()(msg);
|
||||
}
|
||||
|
||||
void
|
||||
cubeb_log_set(cubeb_log_level log_level, cubeb_log_callback log_callback)
|
||||
{
|
||||
g_cubeb_log_level = log_level;
|
||||
// Once a callback has a been set, `g_cubeb_log_callback` is never set back to
|
||||
// nullptr, to prevent a TOCTOU race between checking the pointer
|
||||
if (log_callback && log_level != CUBEB_LOG_DISABLED) {
|
||||
g_cubeb_log_callback = log_callback;
|
||||
} else if (!log_callback || CUBEB_LOG_DISABLED) {
|
||||
g_cubeb_log_callback = cubeb_noop_log_callback;
|
||||
} else {
|
||||
assert(false && "Incorrect parameters passed to cubeb_log_set");
|
||||
}
|
||||
}
|
||||
|
||||
cubeb_log_level
|
||||
cubeb_log_get_level()
|
||||
{
|
||||
return g_cubeb_log_level;
|
||||
}
|
||||
|
||||
cubeb_log_callback
|
||||
cubeb_log_get_callback()
|
||||
{
|
||||
if (g_cubeb_log_callback == cubeb_noop_log_callback) {
|
||||
return nullptr;
|
||||
}
|
||||
return g_cubeb_log_callback;
|
||||
}
|
||||
|
|
|
@ -30,12 +30,16 @@ extern "C" {
|
|||
(strrchr(__FILE__, '/') ? strrchr(__FILE__, '/') + 1 : __FILE__)
|
||||
#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, ...);
|
||||
cubeb_log_set(cubeb_log_level log_level, cubeb_log_callback log_callback);
|
||||
cubeb_log_level
|
||||
cubeb_log_get_level(void);
|
||||
cubeb_log_callback
|
||||
cubeb_log_get_callback(void);
|
||||
void
|
||||
cubeb_async_log_reset_threads(void);
|
||||
cubeb_log_internal_no_format(const char * msg);
|
||||
void
|
||||
cubeb_log_internal(const char * filename, uint32_t line, const char * fmt, ...);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
@ -44,31 +48,16 @@ cubeb_async_log_reset_threads(void);
|
|||
#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_NO_FORMAT(level, fmt, ...) \
|
||||
do { \
|
||||
if (g_cubeb_log_callback && level <= g_cubeb_log_level) { \
|
||||
g_cubeb_log_callback(fmt, __VA_ARGS__); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
#define LOG_INTERNAL(level, fmt, ...) \
|
||||
do { \
|
||||
if (g_cubeb_log_callback && level <= g_cubeb_log_level) { \
|
||||
g_cubeb_log_callback("%s:%d: " fmt "\n", __FILENAME__, __LINE__, \
|
||||
##__VA_ARGS__); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
#define ALOG_INTERNAL(level, fmt, ...) \
|
||||
do { \
|
||||
if (level <= g_cubeb_log_level) { \
|
||||
cubeb_async_log(fmt, ##__VA_ARGS__); \
|
||||
if (cubeb_log_get_level() >= level && cubeb_log_get_callback()) { \
|
||||
cubeb_log_internal(__FILENAME__, __LINE__, fmt, ##__VA_ARGS__); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
/* Asynchronous logging macros to log in real-time callbacks. */
|
||||
/* Should not be used on android due to the use of global/static variables. */
|
||||
#define ALOGV(msg, ...) ALOG_INTERNAL(CUBEB_LOG_VERBOSE, msg, ##__VA_ARGS__)
|
||||
#define ALOG(msg, ...) ALOG_INTERNAL(CUBEB_LOG_NORMAL, msg, ##__VA_ARGS__)
|
||||
#define ALOGV(msg, ...) LOG_INTERNAL(CUBEB_LOG_VERBOSE, msg, ##__VA_ARGS__)
|
||||
#define ALOG(msg, ...) LOG_INTERNAL(CUBEB_LOG_NORMAL, msg, ##__VA_ARGS__)
|
||||
|
||||
#endif // CUBEB_LOG
|
||||
|
|
|
@ -183,7 +183,7 @@ MixerContext::auto_matrix()
|
|||
{
|
||||
double matrix[NUM_NAMED_CHANNELS][NUM_NAMED_CHANNELS] = {{0}};
|
||||
double maxcoef = 0;
|
||||
float maxval;
|
||||
double maxval;
|
||||
|
||||
cubeb_channel_layout in_ch_layout = clean_layout(_in_ch_layout);
|
||||
cubeb_channel_layout out_ch_layout = clean_layout(_out_ch_layout);
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -14,6 +14,7 @@
|
|||
#include "cubeb/cubeb.h"
|
||||
#include "cubeb_mixer.h"
|
||||
#include "cubeb_strings.h"
|
||||
#include "cubeb_tracing.h"
|
||||
#include <assert.h>
|
||||
#include <ctype.h>
|
||||
#include <errno.h>
|
||||
|
@ -975,6 +976,8 @@ oss_io_routine(void * arg)
|
|||
cubeb_state new_state;
|
||||
int stopped;
|
||||
|
||||
CUBEB_REGISTER_THREAD("cubeb rendering thread");
|
||||
|
||||
do {
|
||||
pthread_mutex_lock(&s->mtx);
|
||||
if (s->destroying) {
|
||||
|
@ -1005,6 +1008,9 @@ oss_io_routine(void * arg)
|
|||
pthread_mutex_lock(&s->mtx);
|
||||
s->thread_created = false;
|
||||
pthread_mutex_unlock(&s->mtx);
|
||||
|
||||
CUBEB_UNREGISTER_THREAD();
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
|
|
@ -804,10 +804,11 @@ pulse_destroy(cubeb * ctx)
|
|||
if (ctx->device_ids) {
|
||||
cubeb_strings_destroy(ctx->device_ids);
|
||||
}
|
||||
|
||||
#ifndef DISABLE_LIBPULSE_DLOPEN
|
||||
if (ctx->libpulse) {
|
||||
dlclose(ctx->libpulse);
|
||||
}
|
||||
#endif
|
||||
free(ctx->default_sink_info);
|
||||
free(ctx);
|
||||
}
|
||||
|
@ -1024,7 +1025,7 @@ pulse_stream_init(cubeb * context, cubeb_stream ** stream,
|
|||
return CUBEB_ERROR;
|
||||
}
|
||||
|
||||
if (g_cubeb_log_level) {
|
||||
if (cubeb_log_get_level()) {
|
||||
if (output_stream_params) {
|
||||
const pa_buffer_attr * output_att;
|
||||
output_att = WRAP(pa_stream_get_buffer_attr)(stm->output_stream);
|
||||
|
@ -1577,7 +1578,7 @@ pulse_subscribe_callback(pa_context * ctx, pa_subscription_event_type_t t,
|
|||
case PA_SUBSCRIPTION_EVENT_SOURCE:
|
||||
case PA_SUBSCRIPTION_EVENT_SINK:
|
||||
|
||||
if (g_cubeb_log_level) {
|
||||
if (cubeb_log_get_level()) {
|
||||
if ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) ==
|
||||
PA_SUBSCRIPTION_EVENT_SOURCE &&
|
||||
(t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) ==
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
#define CUBEB_RING_ARRAY_H
|
||||
|
||||
#include "cubeb_utils.h"
|
||||
#include <CoreAudio/CoreAudioTypes.h>
|
||||
|
||||
/** Ring array of pointers is used to hold buffers. In case that
|
||||
asynchronous producer/consumer callbacks do not arrive in a
|
||||
|
@ -46,7 +47,7 @@ single_audiobuffer_init(AudioBuffer * buffer, uint32_t bytesPerFrame,
|
|||
/** Initialize the ring array.
|
||||
@param ra The ring_array pointer of allocated structure.
|
||||
@retval 0 on success. */
|
||||
int
|
||||
static int
|
||||
ring_array_init(ring_array * ra, uint32_t capacity, uint32_t bytesPerFrame,
|
||||
uint32_t channelsPerFrame, uint32_t framesPerBuffer)
|
||||
{
|
||||
|
@ -78,7 +79,7 @@ ring_array_init(ring_array * ra, uint32_t capacity, uint32_t bytesPerFrame,
|
|||
|
||||
/** Destroy the ring array.
|
||||
@param ra The ring_array pointer.*/
|
||||
void
|
||||
static void
|
||||
ring_array_destroy(ring_array * ra)
|
||||
{
|
||||
assert(ra);
|
||||
|
@ -97,7 +98,7 @@ ring_array_destroy(ring_array * ra)
|
|||
@param ra The ring_array pointer.
|
||||
@retval Pointer of the allocated space to be stored with fresh data or NULL
|
||||
if full. */
|
||||
AudioBuffer *
|
||||
static AudioBuffer *
|
||||
ring_array_get_free_buffer(ring_array * ra)
|
||||
{
|
||||
assert(ra && ra->buffer_array);
|
||||
|
@ -118,7 +119,7 @@ ring_array_get_free_buffer(ring_array * ra)
|
|||
/** 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 *
|
||||
static AudioBuffer *
|
||||
ring_array_get_data_buffer(ring_array * ra)
|
||||
{
|
||||
assert(ra && ra->buffer_array);
|
||||
|
@ -138,18 +139,4 @@ ring_array_get_data_buffer(ring_array * ra)
|
|||
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
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
*/
|
||||
#include "cubeb-internal.h"
|
||||
#include "cubeb/cubeb.h"
|
||||
#include "cubeb_tracing.h"
|
||||
#include <assert.h>
|
||||
#include <dlfcn.h>
|
||||
#include <inttypes.h>
|
||||
|
@ -67,7 +68,7 @@ struct cubeb_stream {
|
|||
struct sio_hdl * hdl; /* link us to sndio */
|
||||
int mode; /* bitmap of SIO_{PLAY,REC} */
|
||||
int active; /* cubec_start() called */
|
||||
int conv; /* need float->s16 conversion */
|
||||
int conv; /* need float->s24 conversion */
|
||||
unsigned char * rbuf; /* rec data consumed from here */
|
||||
unsigned char * pbuf; /* play data is prepared here */
|
||||
unsigned int nfr; /* number of frames in ibuf and obuf */
|
||||
|
@ -98,33 +99,33 @@ s16_setvol(void * ptr, long nsamp, float volume)
|
|||
}
|
||||
|
||||
static void
|
||||
float_to_s16(void * ptr, long nsamp, float volume)
|
||||
float_to_s24(void * ptr, long nsamp, float volume)
|
||||
{
|
||||
int16_t * dst = ptr;
|
||||
int32_t * dst = ptr;
|
||||
float * src = ptr;
|
||||
float mult = volume * 32768;
|
||||
float mult = volume * 8388608;
|
||||
int s;
|
||||
|
||||
while (nsamp-- > 0) {
|
||||
s = lrintf(*(src++) * mult);
|
||||
if (s < -32768)
|
||||
s = -32768;
|
||||
else if (s > 32767)
|
||||
s = 32767;
|
||||
if (s < -8388608)
|
||||
s = -8388608;
|
||||
else if (s > 8388607)
|
||||
s = 8388607;
|
||||
*(dst++) = s;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
s16_to_float(void * ptr, long nsamp)
|
||||
s24_to_float(void * ptr, long nsamp)
|
||||
{
|
||||
int16_t * src = ptr;
|
||||
int32_t * src = ptr;
|
||||
float * dst = ptr;
|
||||
|
||||
src += nsamp;
|
||||
dst += nsamp;
|
||||
while (nsamp-- > 0)
|
||||
*(--dst) = (1. / 32768) * *(--src);
|
||||
*(--dst) = (1. / 8388608) * *(--src);
|
||||
}
|
||||
|
||||
static const char *
|
||||
|
@ -161,10 +162,14 @@ sndio_mainloop(void * arg)
|
|||
size_t pstart = 0, pend = 0, rstart = 0, rend = 0;
|
||||
long nfr;
|
||||
|
||||
CUBEB_REGISTER_THREAD("cubeb rendering thread");
|
||||
|
||||
nfds = WRAP(sio_nfds)(s->hdl);
|
||||
pfds = calloc(nfds, sizeof(struct pollfd));
|
||||
if (pfds == NULL)
|
||||
if (pfds == NULL) {
|
||||
CUBEB_UNREGISTER_THREAD();
|
||||
return NULL;
|
||||
}
|
||||
|
||||
DPR("sndio_mainloop()\n");
|
||||
s->state_cb(s, s->arg, CUBEB_STATE_STARTED);
|
||||
|
@ -172,6 +177,7 @@ sndio_mainloop(void * arg)
|
|||
if (!WRAP(sio_start)(s->hdl)) {
|
||||
pthread_mutex_unlock(&s->mtx);
|
||||
free(pfds);
|
||||
CUBEB_UNREGISTER_THREAD();
|
||||
return NULL;
|
||||
}
|
||||
DPR("sndio_mainloop(), started\n");
|
||||
|
@ -207,7 +213,7 @@ sndio_mainloop(void * arg)
|
|||
}
|
||||
|
||||
if ((s->mode & SIO_REC) && s->conv)
|
||||
s16_to_float(s->rbuf, s->nfr * s->rchan);
|
||||
s24_to_float(s->rbuf, s->nfr * s->rchan);
|
||||
|
||||
/* invoke call-back, it returns less that s->nfr if done */
|
||||
pthread_mutex_unlock(&s->mtx);
|
||||
|
@ -238,7 +244,7 @@ sndio_mainloop(void * arg)
|
|||
|
||||
if (s->mode & SIO_PLAY) {
|
||||
if (s->conv)
|
||||
float_to_s16(s->pbuf, nfr * s->pchan, s->volume);
|
||||
float_to_s24(s->pbuf, nfr * s->pchan, s->volume);
|
||||
else
|
||||
s16_setvol(s->pbuf, nfr * s->pchan, s->volume);
|
||||
}
|
||||
|
@ -300,6 +306,7 @@ sndio_mainloop(void * arg)
|
|||
pthread_mutex_unlock(&s->mtx);
|
||||
s->state_cb(s, s->arg, state);
|
||||
free(pfds);
|
||||
CUBEB_UNREGISTER_THREAD();
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
@ -362,8 +369,10 @@ static void
|
|||
sndio_destroy(cubeb * context)
|
||||
{
|
||||
DPR("sndio_destroy()\n");
|
||||
#ifndef DISABLE_LIBSNDIO_DLOPEN
|
||||
if (context->libsndio)
|
||||
dlclose(context->libsndio);
|
||||
#endif
|
||||
free(context);
|
||||
}
|
||||
|
||||
|
@ -420,21 +429,25 @@ sndio_stream_init(cubeb * context, cubeb_stream ** stream,
|
|||
}
|
||||
WRAP(sio_initpar)(&wpar);
|
||||
wpar.sig = 1;
|
||||
wpar.bits = 16;
|
||||
switch (format) {
|
||||
case CUBEB_SAMPLE_S16LE:
|
||||
wpar.le = 1;
|
||||
wpar.bits = 16;
|
||||
break;
|
||||
case CUBEB_SAMPLE_S16BE:
|
||||
wpar.le = 0;
|
||||
wpar.bits = 16;
|
||||
break;
|
||||
case CUBEB_SAMPLE_FLOAT32NE:
|
||||
wpar.le = SIO_LE_NATIVE;
|
||||
wpar.bits = 24;
|
||||
wpar.msb = 0;
|
||||
break;
|
||||
default:
|
||||
DPR("sndio_stream_init() unsupported format\n");
|
||||
goto err;
|
||||
}
|
||||
wpar.bps = SIO_BPS(wpar.bits);
|
||||
wpar.rate = rate;
|
||||
if (s->mode & SIO_REC)
|
||||
wpar.rchan = input_stream_params->channels;
|
||||
|
@ -446,6 +459,8 @@ sndio_stream_init(cubeb * context, cubeb_stream ** stream,
|
|||
goto err;
|
||||
}
|
||||
if (rpar.bits != wpar.bits || rpar.le != wpar.le || rpar.sig != wpar.sig ||
|
||||
rpar.bps != wpar.bps ||
|
||||
(wpar.bits < 8 * wpar.bps && rpar.msb != wpar.msb) ||
|
||||
rpar.rate != wpar.rate ||
|
||||
((s->mode & SIO_REC) && rpar.rchan != wpar.rchan) ||
|
||||
((s->mode & SIO_PLAY) && rpar.pchan != wpar.pchan)) {
|
||||
|
|
|
@ -1,733 +0,0 @@
|
|||
/*
|
||||
* Copyright © 2019-2020 Nia Alarie <nia@NetBSD.org>
|
||||
*
|
||||
* This program is made available under an ISC-style license. See the
|
||||
* accompanying file LICENSE for details.
|
||||
*/
|
||||
#include "cubeb-internal.h"
|
||||
#include "cubeb/cubeb.h"
|
||||
#include <fcntl.h>
|
||||
#include <limits.h>
|
||||
#include <pthread.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/audioio.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <unistd.h>
|
||||
|
||||
/* Default to 4 + 1 for the default device. */
|
||||
#ifndef SUN_DEVICE_COUNT
|
||||
#define SUN_DEVICE_COUNT (5)
|
||||
#endif
|
||||
|
||||
/* Supported well by most hardware. */
|
||||
#ifndef SUN_PREFER_RATE
|
||||
#define SUN_PREFER_RATE (48000)
|
||||
#endif
|
||||
|
||||
/* Standard acceptable minimum. */
|
||||
#ifndef SUN_LATENCY_MS
|
||||
#define SUN_LATENCY_MS (40)
|
||||
#endif
|
||||
|
||||
#ifndef SUN_DEFAULT_DEVICE
|
||||
#define SUN_DEFAULT_DEVICE "/dev/audio"
|
||||
#endif
|
||||
|
||||
#ifndef SUN_BUFFER_FRAMES
|
||||
#define SUN_BUFFER_FRAMES (32)
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Supported on NetBSD regardless of hardware.
|
||||
*/
|
||||
|
||||
#ifndef SUN_MAX_CHANNELS
|
||||
#ifdef __NetBSD__
|
||||
#define SUN_MAX_CHANNELS (12)
|
||||
#else
|
||||
#define SUN_MAX_CHANNELS (2)
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifndef SUN_MIN_RATE
|
||||
#define SUN_MIN_RATE (1000)
|
||||
#endif
|
||||
|
||||
#ifndef SUN_MAX_RATE
|
||||
#define SUN_MAX_RATE (192000)
|
||||
#endif
|
||||
|
||||
static struct cubeb_ops const sun_ops;
|
||||
|
||||
struct cubeb {
|
||||
struct cubeb_ops const * ops;
|
||||
};
|
||||
|
||||
struct sun_stream {
|
||||
char name[32];
|
||||
int fd;
|
||||
void * buf;
|
||||
struct audio_info info;
|
||||
unsigned frame_size; /* precision in bytes * channels */
|
||||
bool floating;
|
||||
};
|
||||
|
||||
struct cubeb_stream {
|
||||
struct cubeb * context;
|
||||
void * user_ptr;
|
||||
pthread_t thread;
|
||||
pthread_mutex_t mutex; /* protects running, volume, frames_written */
|
||||
bool running;
|
||||
float volume;
|
||||
struct sun_stream play;
|
||||
struct sun_stream record;
|
||||
cubeb_data_callback data_cb;
|
||||
cubeb_state_callback state_cb;
|
||||
uint64_t frames_written;
|
||||
uint64_t blocks_written;
|
||||
};
|
||||
|
||||
int
|
||||
sun_init(cubeb ** context, char const * context_name)
|
||||
{
|
||||
cubeb * c;
|
||||
|
||||
(void)context_name;
|
||||
if ((c = calloc(1, sizeof(cubeb))) == NULL) {
|
||||
return CUBEB_ERROR;
|
||||
}
|
||||
c->ops = &sun_ops;
|
||||
*context = c;
|
||||
return CUBEB_OK;
|
||||
}
|
||||
|
||||
static void
|
||||
sun_destroy(cubeb * context)
|
||||
{
|
||||
free(context);
|
||||
}
|
||||
|
||||
static char const *
|
||||
sun_get_backend_id(cubeb * context)
|
||||
{
|
||||
return "sun";
|
||||
}
|
||||
|
||||
static int
|
||||
sun_get_preferred_sample_rate(cubeb * context, uint32_t * rate)
|
||||
{
|
||||
(void)context;
|
||||
|
||||
*rate = SUN_PREFER_RATE;
|
||||
return CUBEB_OK;
|
||||
}
|
||||
|
||||
static int
|
||||
sun_get_max_channel_count(cubeb * context, uint32_t * max_channels)
|
||||
{
|
||||
(void)context;
|
||||
|
||||
*max_channels = SUN_MAX_CHANNELS;
|
||||
return CUBEB_OK;
|
||||
}
|
||||
|
||||
static int
|
||||
sun_get_min_latency(cubeb * context, cubeb_stream_params params,
|
||||
uint32_t * latency_frames)
|
||||
{
|
||||
(void)context;
|
||||
|
||||
*latency_frames = SUN_LATENCY_MS * params.rate / 1000;
|
||||
return CUBEB_OK;
|
||||
}
|
||||
|
||||
static int
|
||||
sun_get_hwinfo(const char * device, struct audio_info * format, int * props,
|
||||
struct audio_device * dev)
|
||||
{
|
||||
int fd = -1;
|
||||
|
||||
if ((fd = open(device, O_RDONLY)) == -1) {
|
||||
goto error;
|
||||
}
|
||||
#ifdef AUDIO_GETFORMAT
|
||||
if (ioctl(fd, AUDIO_GETFORMAT, format) != 0) {
|
||||
goto error;
|
||||
}
|
||||
#endif
|
||||
#ifdef AUDIO_GETPROPS
|
||||
if (ioctl(fd, AUDIO_GETPROPS, props) != 0) {
|
||||
goto error;
|
||||
}
|
||||
#endif
|
||||
if (ioctl(fd, AUDIO_GETDEV, dev) != 0) {
|
||||
goto error;
|
||||
}
|
||||
close(fd);
|
||||
return CUBEB_OK;
|
||||
error:
|
||||
if (fd != -1) {
|
||||
close(fd);
|
||||
}
|
||||
return CUBEB_ERROR;
|
||||
}
|
||||
|
||||
/*
|
||||
* XXX: PR kern/54264
|
||||
*/
|
||||
static int
|
||||
sun_prinfo_verify_sanity(struct audio_prinfo * prinfo)
|
||||
{
|
||||
return prinfo->precision >= 8 && prinfo->precision <= 32 &&
|
||||
prinfo->channels >= 1 && prinfo->channels < SUN_MAX_CHANNELS &&
|
||||
prinfo->sample_rate < SUN_MAX_RATE &&
|
||||
prinfo->sample_rate > SUN_MIN_RATE;
|
||||
}
|
||||
|
||||
static int
|
||||
sun_enumerate_devices(cubeb * context, cubeb_device_type type,
|
||||
cubeb_device_collection * collection)
|
||||
{
|
||||
unsigned i;
|
||||
cubeb_device_info device = {0};
|
||||
char dev[16] = SUN_DEFAULT_DEVICE;
|
||||
char dev_friendly[64];
|
||||
struct audio_info hwfmt;
|
||||
struct audio_device hwname;
|
||||
struct audio_prinfo * prinfo = NULL;
|
||||
int hwprops;
|
||||
|
||||
collection->device = calloc(SUN_DEVICE_COUNT, sizeof(cubeb_device_info));
|
||||
if (collection->device == NULL) {
|
||||
return CUBEB_ERROR;
|
||||
}
|
||||
collection->count = 0;
|
||||
|
||||
for (i = 0; i < SUN_DEVICE_COUNT; ++i) {
|
||||
if (i > 0) {
|
||||
(void)snprintf(dev, sizeof(dev), "/dev/audio%u", i - 1);
|
||||
}
|
||||
if (sun_get_hwinfo(dev, &hwfmt, &hwprops, &hwname) != CUBEB_OK) {
|
||||
continue;
|
||||
}
|
||||
#ifdef AUDIO_GETPROPS
|
||||
device.type = 0;
|
||||
if ((hwprops & AUDIO_PROP_CAPTURE) != 0 &&
|
||||
sun_prinfo_verify_sanity(&hwfmt.record)) {
|
||||
/* the device supports recording, probably */
|
||||
device.type |= CUBEB_DEVICE_TYPE_INPUT;
|
||||
}
|
||||
if ((hwprops & AUDIO_PROP_PLAYBACK) != 0 &&
|
||||
sun_prinfo_verify_sanity(&hwfmt.play)) {
|
||||
/* the device supports playback, probably */
|
||||
device.type |= CUBEB_DEVICE_TYPE_OUTPUT;
|
||||
}
|
||||
switch (device.type) {
|
||||
case 0:
|
||||
/* device doesn't do input or output, aliens probably involved */
|
||||
continue;
|
||||
case CUBEB_DEVICE_TYPE_INPUT:
|
||||
if ((type & CUBEB_DEVICE_TYPE_INPUT) == 0) {
|
||||
/* this device is input only, not scanning for those, skip it */
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
case CUBEB_DEVICE_TYPE_OUTPUT:
|
||||
if ((type & CUBEB_DEVICE_TYPE_OUTPUT) == 0) {
|
||||
/* this device is output only, not scanning for those, skip it */
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
if ((type & CUBEB_DEVICE_TYPE_INPUT) != 0) {
|
||||
prinfo = &hwfmt.record;
|
||||
}
|
||||
if ((type & CUBEB_DEVICE_TYPE_OUTPUT) != 0) {
|
||||
prinfo = &hwfmt.play;
|
||||
}
|
||||
#endif
|
||||
if (i > 0) {
|
||||
(void)snprintf(dev_friendly, sizeof(dev_friendly), "%s %s %s (%d)",
|
||||
hwname.name, hwname.version, hwname.config, i - 1);
|
||||
} else {
|
||||
(void)snprintf(dev_friendly, sizeof(dev_friendly), "%s %s %s (default)",
|
||||
hwname.name, hwname.version, hwname.config);
|
||||
}
|
||||
device.devid = (void *)(uintptr_t)i;
|
||||
device.device_id = strdup(dev);
|
||||
device.friendly_name = strdup(dev_friendly);
|
||||
device.group_id = strdup(dev);
|
||||
device.vendor_name = strdup(hwname.name);
|
||||
device.type = type;
|
||||
device.state = CUBEB_DEVICE_STATE_ENABLED;
|
||||
device.preferred =
|
||||
(i == 0) ? CUBEB_DEVICE_PREF_ALL : CUBEB_DEVICE_PREF_NONE;
|
||||
#ifdef AUDIO_GETFORMAT
|
||||
device.max_channels = prinfo->channels;
|
||||
device.default_rate = prinfo->sample_rate;
|
||||
#else
|
||||
device.max_channels = 2;
|
||||
device.default_rate = SUN_PREFER_RATE;
|
||||
#endif
|
||||
device.default_format = CUBEB_DEVICE_FMT_S16NE;
|
||||
device.format = CUBEB_DEVICE_FMT_S16NE;
|
||||
device.min_rate = SUN_MIN_RATE;
|
||||
device.max_rate = SUN_MAX_RATE;
|
||||
device.latency_lo = SUN_LATENCY_MS * SUN_MIN_RATE / 1000;
|
||||
device.latency_hi = SUN_LATENCY_MS * SUN_MAX_RATE / 1000;
|
||||
collection->device[collection->count++] = device;
|
||||
}
|
||||
return CUBEB_OK;
|
||||
}
|
||||
|
||||
static int
|
||||
sun_device_collection_destroy(cubeb * context,
|
||||
cubeb_device_collection * collection)
|
||||
{
|
||||
unsigned i;
|
||||
|
||||
for (i = 0; i < collection->count; ++i) {
|
||||
free((char *)collection->device[i].device_id);
|
||||
free((char *)collection->device[i].friendly_name);
|
||||
free((char *)collection->device[i].group_id);
|
||||
free((char *)collection->device[i].vendor_name);
|
||||
}
|
||||
free(collection->device);
|
||||
return CUBEB_OK;
|
||||
}
|
||||
|
||||
static int
|
||||
sun_copy_params(int fd, cubeb_stream * stream, cubeb_stream_params * params,
|
||||
struct audio_info * info, struct audio_prinfo * prinfo)
|
||||
{
|
||||
prinfo->channels = params->channels;
|
||||
prinfo->sample_rate = params->rate;
|
||||
#ifdef AUDIO_ENCODING_SLINEAR_LE
|
||||
switch (params->format) {
|
||||
case CUBEB_SAMPLE_S16LE:
|
||||
prinfo->encoding = AUDIO_ENCODING_SLINEAR_LE;
|
||||
prinfo->precision = 16;
|
||||
break;
|
||||
case CUBEB_SAMPLE_S16BE:
|
||||
prinfo->encoding = AUDIO_ENCODING_SLINEAR_BE;
|
||||
prinfo->precision = 16;
|
||||
break;
|
||||
case CUBEB_SAMPLE_FLOAT32NE:
|
||||
prinfo->encoding = AUDIO_ENCODING_SLINEAR;
|
||||
prinfo->precision = 32;
|
||||
break;
|
||||
default:
|
||||
LOG("Unsupported format");
|
||||
return CUBEB_ERROR_INVALID_FORMAT;
|
||||
}
|
||||
#else
|
||||
switch (params->format) {
|
||||
case CUBEB_SAMPLE_S16NE:
|
||||
prinfo->encoding = AUDIO_ENCODING_LINEAR;
|
||||
prinfo->precision = 16;
|
||||
break;
|
||||
case CUBEB_SAMPLE_FLOAT32NE:
|
||||
prinfo->encoding = AUDIO_ENCODING_LINEAR;
|
||||
prinfo->precision = 32;
|
||||
break;
|
||||
default:
|
||||
LOG("Unsupported format");
|
||||
return CUBEB_ERROR_INVALID_FORMAT;
|
||||
}
|
||||
#endif
|
||||
if (ioctl(fd, AUDIO_SETINFO, info) == -1) {
|
||||
return CUBEB_ERROR;
|
||||
}
|
||||
if (ioctl(fd, AUDIO_GETINFO, info) == -1) {
|
||||
return CUBEB_ERROR;
|
||||
}
|
||||
return CUBEB_OK;
|
||||
}
|
||||
|
||||
static int
|
||||
sun_stream_stop(cubeb_stream * s)
|
||||
{
|
||||
pthread_mutex_lock(&s->mutex);
|
||||
if (s->running) {
|
||||
s->running = false;
|
||||
pthread_mutex_unlock(&s->mutex);
|
||||
pthread_join(s->thread, NULL);
|
||||
} else {
|
||||
pthread_mutex_unlock(&s->mutex);
|
||||
}
|
||||
return CUBEB_OK;
|
||||
}
|
||||
|
||||
static void
|
||||
sun_stream_destroy(cubeb_stream * s)
|
||||
{
|
||||
sun_stream_stop(s);
|
||||
pthread_mutex_destroy(&s->mutex);
|
||||
if (s->play.fd != -1) {
|
||||
close(s->play.fd);
|
||||
}
|
||||
if (s->record.fd != -1) {
|
||||
close(s->record.fd);
|
||||
}
|
||||
free(s->play.buf);
|
||||
free(s->record.buf);
|
||||
free(s);
|
||||
}
|
||||
|
||||
static void
|
||||
sun_float_to_linear32(void * buf, unsigned sample_count, float vol)
|
||||
{
|
||||
float * in = buf;
|
||||
int32_t * out = buf;
|
||||
int32_t * tail = out + sample_count;
|
||||
|
||||
while (out < tail) {
|
||||
float f = *(in++) * vol;
|
||||
if (f < -1.0)
|
||||
f = -1.0;
|
||||
else if (f > 1.0)
|
||||
f = 1.0;
|
||||
*(out++) = f * (float)INT32_MAX;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
sun_linear32_to_float(void * buf, unsigned sample_count)
|
||||
{
|
||||
int32_t * in = buf;
|
||||
float * out = buf;
|
||||
float * tail = out + sample_count;
|
||||
|
||||
while (out < tail) {
|
||||
*(out++) = (1.0 / 0x80000000) * *(in++);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
sun_linear16_set_vol(int16_t * buf, unsigned sample_count, float vol)
|
||||
{
|
||||
unsigned i;
|
||||
int32_t multiplier = vol * 0x8000;
|
||||
|
||||
for (i = 0; i < sample_count; ++i) {
|
||||
buf[i] = (buf[i] * multiplier) >> 15;
|
||||
}
|
||||
}
|
||||
|
||||
static void *
|
||||
sun_io_routine(void * arg)
|
||||
{
|
||||
cubeb_stream * s = arg;
|
||||
cubeb_state state = CUBEB_STATE_STARTED;
|
||||
size_t to_read = 0;
|
||||
long to_write = 0;
|
||||
size_t write_ofs = 0;
|
||||
size_t read_ofs = 0;
|
||||
int drain = 0;
|
||||
|
||||
s->state_cb(s, s->user_ptr, CUBEB_STATE_STARTED);
|
||||
while (state != CUBEB_STATE_ERROR) {
|
||||
pthread_mutex_lock(&s->mutex);
|
||||
if (!s->running) {
|
||||
pthread_mutex_unlock(&s->mutex);
|
||||
state = CUBEB_STATE_STOPPED;
|
||||
break;
|
||||
}
|
||||
pthread_mutex_unlock(&s->mutex);
|
||||
if (s->record.fd != -1 && s->record.floating) {
|
||||
sun_linear32_to_float(s->record.buf,
|
||||
s->record.info.record.channels * SUN_BUFFER_FRAMES);
|
||||
}
|
||||
to_write = s->data_cb(s, s->user_ptr, s->record.buf, s->play.buf,
|
||||
SUN_BUFFER_FRAMES);
|
||||
if (to_write == CUBEB_ERROR) {
|
||||
state = CUBEB_STATE_ERROR;
|
||||
break;
|
||||
}
|
||||
if (s->play.fd != -1) {
|
||||
float vol;
|
||||
|
||||
pthread_mutex_lock(&s->mutex);
|
||||
vol = s->volume;
|
||||
pthread_mutex_unlock(&s->mutex);
|
||||
|
||||
if (s->play.floating) {
|
||||
sun_float_to_linear32(s->play.buf,
|
||||
s->play.info.play.channels * to_write, vol);
|
||||
} else {
|
||||
sun_linear16_set_vol(s->play.buf, s->play.info.play.channels * to_write,
|
||||
vol);
|
||||
}
|
||||
}
|
||||
if (to_write < SUN_BUFFER_FRAMES) {
|
||||
drain = 1;
|
||||
}
|
||||
to_write = s->play.fd != -1 ? to_write : 0;
|
||||
to_read = s->record.fd != -1 ? SUN_BUFFER_FRAMES : 0;
|
||||
write_ofs = 0;
|
||||
read_ofs = 0;
|
||||
while (to_write > 0 || to_read > 0) {
|
||||
size_t bytes;
|
||||
ssize_t n, frames;
|
||||
|
||||
if (to_write > 0) {
|
||||
bytes = to_write * s->play.frame_size;
|
||||
if ((n = write(s->play.fd, (uint8_t *)s->play.buf + write_ofs, bytes)) <
|
||||
0) {
|
||||
state = CUBEB_STATE_ERROR;
|
||||
break;
|
||||
}
|
||||
frames = n / s->play.frame_size;
|
||||
pthread_mutex_lock(&s->mutex);
|
||||
s->frames_written += frames;
|
||||
pthread_mutex_unlock(&s->mutex);
|
||||
to_write -= frames;
|
||||
write_ofs += n;
|
||||
}
|
||||
if (to_read > 0) {
|
||||
bytes = to_read * s->record.frame_size;
|
||||
if ((n = read(s->record.fd, (uint8_t *)s->record.buf + read_ofs,
|
||||
bytes)) < 0) {
|
||||
state = CUBEB_STATE_ERROR;
|
||||
break;
|
||||
}
|
||||
frames = n / s->record.frame_size;
|
||||
to_read -= frames;
|
||||
read_ofs += n;
|
||||
}
|
||||
}
|
||||
if (drain && state != CUBEB_STATE_ERROR) {
|
||||
state = CUBEB_STATE_DRAINED;
|
||||
break;
|
||||
}
|
||||
}
|
||||
s->state_cb(s, s->user_ptr, state);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int
|
||||
sun_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 latency_frames, cubeb_data_callback data_callback,
|
||||
cubeb_state_callback state_callback, void * user_ptr)
|
||||
{
|
||||
int ret = CUBEB_OK;
|
||||
cubeb_stream * s = NULL;
|
||||
|
||||
(void)stream_name;
|
||||
(void)latency_frames;
|
||||
if ((s = calloc(1, sizeof(cubeb_stream))) == NULL) {
|
||||
ret = CUBEB_ERROR;
|
||||
goto error;
|
||||
}
|
||||
s->record.fd = -1;
|
||||
s->play.fd = -1;
|
||||
if (input_device != 0) {
|
||||
snprintf(s->record.name, sizeof(s->record.name), "/dev/audio%zu",
|
||||
(uintptr_t)input_device - 1);
|
||||
} else {
|
||||
snprintf(s->record.name, sizeof(s->record.name), "%s", SUN_DEFAULT_DEVICE);
|
||||
}
|
||||
if (output_device != 0) {
|
||||
snprintf(s->play.name, sizeof(s->play.name), "/dev/audio%zu",
|
||||
(uintptr_t)output_device - 1);
|
||||
} else {
|
||||
snprintf(s->play.name, sizeof(s->play.name), "%s", SUN_DEFAULT_DEVICE);
|
||||
}
|
||||
if (input_stream_params != NULL) {
|
||||
if (input_stream_params->prefs & CUBEB_STREAM_PREF_LOOPBACK) {
|
||||
LOG("Loopback not supported");
|
||||
ret = CUBEB_ERROR_NOT_SUPPORTED;
|
||||
goto error;
|
||||
}
|
||||
if (s->record.fd == -1) {
|
||||
if ((s->record.fd = open(s->record.name, O_RDONLY)) == -1) {
|
||||
LOG("Audio device could not be opened as read-only");
|
||||
ret = CUBEB_ERROR_DEVICE_UNAVAILABLE;
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
AUDIO_INITINFO(&s->record.info);
|
||||
#ifdef AUMODE_RECORD
|
||||
s->record.info.mode = AUMODE_RECORD;
|
||||
#endif
|
||||
if ((ret = sun_copy_params(s->record.fd, s, input_stream_params,
|
||||
&s->record.info, &s->record.info.record)) !=
|
||||
CUBEB_OK) {
|
||||
LOG("Setting record params failed");
|
||||
goto error;
|
||||
}
|
||||
s->record.floating =
|
||||
(input_stream_params->format == CUBEB_SAMPLE_FLOAT32NE);
|
||||
}
|
||||
if (output_stream_params != NULL) {
|
||||
if (output_stream_params->prefs & CUBEB_STREAM_PREF_LOOPBACK) {
|
||||
LOG("Loopback not supported");
|
||||
ret = CUBEB_ERROR_NOT_SUPPORTED;
|
||||
goto error;
|
||||
}
|
||||
if (s->play.fd == -1) {
|
||||
if ((s->play.fd = open(s->play.name, O_WRONLY)) == -1) {
|
||||
LOG("Audio device could not be opened as write-only");
|
||||
ret = CUBEB_ERROR_DEVICE_UNAVAILABLE;
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
AUDIO_INITINFO(&s->play.info);
|
||||
#ifdef AUMODE_PLAY
|
||||
s->play.info.mode = AUMODE_PLAY;
|
||||
#endif
|
||||
if ((ret = sun_copy_params(s->play.fd, s, output_stream_params,
|
||||
&s->play.info, &s->play.info.play)) !=
|
||||
CUBEB_OK) {
|
||||
LOG("Setting play params failed");
|
||||
goto error;
|
||||
}
|
||||
s->play.floating = (output_stream_params->format == CUBEB_SAMPLE_FLOAT32NE);
|
||||
}
|
||||
s->context = context;
|
||||
s->volume = 1.0;
|
||||
s->state_cb = state_callback;
|
||||
s->data_cb = data_callback;
|
||||
s->user_ptr = user_ptr;
|
||||
if (pthread_mutex_init(&s->mutex, NULL) != 0) {
|
||||
LOG("Failed to create mutex");
|
||||
goto error;
|
||||
}
|
||||
s->play.frame_size =
|
||||
s->play.info.play.channels * (s->play.info.play.precision / 8);
|
||||
if (s->play.fd != -1 &&
|
||||
(s->play.buf = calloc(SUN_BUFFER_FRAMES, s->play.frame_size)) == NULL) {
|
||||
ret = CUBEB_ERROR;
|
||||
goto error;
|
||||
}
|
||||
s->record.frame_size =
|
||||
s->record.info.record.channels * (s->record.info.record.precision / 8);
|
||||
if (s->record.fd != -1 &&
|
||||
(s->record.buf = calloc(SUN_BUFFER_FRAMES, s->record.frame_size)) ==
|
||||
NULL) {
|
||||
ret = CUBEB_ERROR;
|
||||
goto error;
|
||||
}
|
||||
*stream = s;
|
||||
return CUBEB_OK;
|
||||
error:
|
||||
if (s != NULL) {
|
||||
sun_stream_destroy(s);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int
|
||||
sun_stream_start(cubeb_stream * s)
|
||||
{
|
||||
s->running = true;
|
||||
if (pthread_create(&s->thread, NULL, sun_io_routine, s) != 0) {
|
||||
LOG("Couldn't create thread");
|
||||
return CUBEB_ERROR;
|
||||
}
|
||||
return CUBEB_OK;
|
||||
}
|
||||
|
||||
static int
|
||||
sun_stream_get_position(cubeb_stream * s, uint64_t * position)
|
||||
{
|
||||
#ifdef AUDIO_GETOOFFS
|
||||
struct audio_offset offset;
|
||||
|
||||
if (ioctl(s->play.fd, AUDIO_GETOOFFS, &offset) == -1) {
|
||||
return CUBEB_ERROR;
|
||||
}
|
||||
s->blocks_written += offset.deltablks;
|
||||
*position = (s->blocks_written * s->play.info.blocksize) / s->play.frame_size;
|
||||
return CUBEB_OK;
|
||||
#else
|
||||
pthread_mutex_lock(&s->mutex);
|
||||
*position = s->frames_written;
|
||||
pthread_mutex_unlock(&s->mutex);
|
||||
return CUBEB_OK;
|
||||
#endif
|
||||
}
|
||||
|
||||
static int
|
||||
sun_stream_get_latency(cubeb_stream * s, uint32_t * latency)
|
||||
{
|
||||
#ifdef AUDIO_GETBUFINFO
|
||||
struct audio_info info;
|
||||
|
||||
if (ioctl(s->play.fd, AUDIO_GETBUFINFO, &info) == -1) {
|
||||
return CUBEB_ERROR;
|
||||
}
|
||||
|
||||
*latency = (info.play.seek + info.blocksize) / s->play.frame_size;
|
||||
return CUBEB_OK;
|
||||
#else
|
||||
cubeb_stream_params params;
|
||||
|
||||
params.rate = s->play.info.play.sample_rate;
|
||||
|
||||
return sun_get_min_latency(NULL, params, latency);
|
||||
#endif
|
||||
}
|
||||
|
||||
static int
|
||||
sun_stream_set_volume(cubeb_stream * stream, float volume)
|
||||
{
|
||||
pthread_mutex_lock(&stream->mutex);
|
||||
stream->volume = volume;
|
||||
pthread_mutex_unlock(&stream->mutex);
|
||||
return CUBEB_OK;
|
||||
}
|
||||
|
||||
static int
|
||||
sun_get_current_device(cubeb_stream * stream, cubeb_device ** const device)
|
||||
{
|
||||
*device = calloc(1, sizeof(cubeb_device));
|
||||
if (*device == NULL) {
|
||||
return CUBEB_ERROR;
|
||||
}
|
||||
(*device)->input_name =
|
||||
stream->record.fd != -1 ? strdup(stream->record.name) : NULL;
|
||||
(*device)->output_name =
|
||||
stream->play.fd != -1 ? strdup(stream->play.name) : NULL;
|
||||
return CUBEB_OK;
|
||||
}
|
||||
|
||||
static int
|
||||
sun_stream_device_destroy(cubeb_stream * stream, cubeb_device * device)
|
||||
{
|
||||
(void)stream;
|
||||
free(device->input_name);
|
||||
free(device->output_name);
|
||||
free(device);
|
||||
return CUBEB_OK;
|
||||
}
|
||||
|
||||
static struct cubeb_ops const sun_ops = {
|
||||
.init = sun_init,
|
||||
.get_backend_id = sun_get_backend_id,
|
||||
.get_max_channel_count = sun_get_max_channel_count,
|
||||
.get_min_latency = sun_get_min_latency,
|
||||
.get_preferred_sample_rate = sun_get_preferred_sample_rate,
|
||||
.enumerate_devices = sun_enumerate_devices,
|
||||
.device_collection_destroy = sun_device_collection_destroy,
|
||||
.destroy = sun_destroy,
|
||||
.stream_init = sun_stream_init,
|
||||
.stream_destroy = sun_stream_destroy,
|
||||
.stream_start = sun_stream_start,
|
||||
.stream_stop = sun_stream_stop,
|
||||
.stream_get_position = sun_stream_get_position,
|
||||
.stream_get_latency = sun_stream_get_latency,
|
||||
.stream_get_input_latency = NULL,
|
||||
.stream_set_volume = sun_stream_set_volume,
|
||||
.stream_set_name = NULL,
|
||||
.stream_get_current_device = sun_get_current_device,
|
||||
.stream_device_destroy = sun_stream_device_destroy,
|
||||
.stream_register_device_changed_callback = NULL,
|
||||
.register_device_collection_changed = NULL};
|
80
dep/cubeb/src/cubeb_triple_buffer.h
Normal file
80
dep/cubeb/src/cubeb_triple_buffer.h
Normal file
|
@ -0,0 +1,80 @@
|
|||
/*
|
||||
* Copyright © 2022 Mozilla Foundation
|
||||
*
|
||||
* This program is made available under an ISC-style license. See the
|
||||
* accompanying file LICENSE for details.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Adapted and ported to C++ from https://crates.io/crates/triple_buffer
|
||||
*/
|
||||
|
||||
#ifndef CUBEB_TRIPLE_BUFFER
|
||||
#define CUBEB_TRIPLE_BUFFER
|
||||
|
||||
#include <atomic>
|
||||
|
||||
// Single producer / single consumer wait-free triple buffering
|
||||
// implementation, for when a producer wants to publish data to a consumer
|
||||
// without blocking, but when a queue is wastefull, because it's OK for the
|
||||
// consumer to miss data updates.
|
||||
template <typename T> class triple_buffer {
|
||||
public:
|
||||
// Write a new value into the triple buffer. Returns true if a value was
|
||||
// overwritten.
|
||||
// Producer-side only.
|
||||
bool write(T & input)
|
||||
{
|
||||
storage[input_idx] = input;
|
||||
return publish();
|
||||
}
|
||||
// Get the latest value from the triple buffer.
|
||||
// Consumer-side only.
|
||||
T & read()
|
||||
{
|
||||
update();
|
||||
return storage[output_idx];
|
||||
}
|
||||
// Returns true if a new value has been published by the consumer without
|
||||
// having been consumed yet.
|
||||
// Consumer-side only.
|
||||
bool updated()
|
||||
{
|
||||
return (shared_state.load(std::memory_order_relaxed) & BACK_DIRTY_BIT) != 0;
|
||||
}
|
||||
|
||||
private:
|
||||
// Publish a value to the consumer. Returns true if the data was overwritten
|
||||
// without having been read.
|
||||
bool publish()
|
||||
{
|
||||
auto former_back_idx = shared_state.exchange(input_idx | BACK_DIRTY_BIT,
|
||||
std::memory_order_acq_rel);
|
||||
input_idx = former_back_idx & BACK_INDEX_MASK;
|
||||
return (former_back_idx & BACK_DIRTY_BIT) != 0;
|
||||
}
|
||||
// Get a new value from the producer, if a new value has been produced.
|
||||
bool update()
|
||||
{
|
||||
bool was_updated = updated();
|
||||
if (was_updated) {
|
||||
auto former_back_idx =
|
||||
shared_state.exchange(output_idx, std::memory_order_acq_rel);
|
||||
output_idx = former_back_idx & BACK_INDEX_MASK;
|
||||
}
|
||||
return was_updated;
|
||||
}
|
||||
T storage[3];
|
||||
// Mask used to extract back-buffer index
|
||||
const uint8_t BACK_INDEX_MASK = 0b11;
|
||||
// Bit set by producer to signal updates
|
||||
const uint8_t BACK_DIRTY_BIT = 0b100;
|
||||
// Shared state: a dirty bit, and an index.
|
||||
std::atomic<uint8_t> shared_state = {0};
|
||||
// Output index, private to the consumer.
|
||||
uint8_t output_idx = 1;
|
||||
// Input index, private to the producer.
|
||||
uint8_t input_idx = 2;
|
||||
};
|
||||
|
||||
#endif // CUBEB_TRIPLE_BUFFER
|
|
@ -24,12 +24,17 @@
|
|||
#include <vector>
|
||||
#include <windef.h>
|
||||
#include <windows.h>
|
||||
/* clang-format off */
|
||||
/* These need to be included after windows.h */
|
||||
#include <mmsystem.h>
|
||||
/* clang-format on */
|
||||
|
||||
#include "cubeb-internal.h"
|
||||
#include "cubeb/cubeb.h"
|
||||
#include "cubeb_mixer.h"
|
||||
#include "cubeb_resampler.h"
|
||||
#include "cubeb_strings.h"
|
||||
#include "cubeb_tracing.h"
|
||||
#include "cubeb_utils.h"
|
||||
|
||||
// Windows 10 exposes the IAudioClient3 interface to create low-latency streams.
|
||||
|
@ -96,6 +101,8 @@ namespace {
|
|||
|
||||
const int64_t LATENCY_NOT_AVAILABLE_YET = -1;
|
||||
|
||||
const DWORD DEVICE_CHANGE_DEBOUNCE_MS = 250;
|
||||
|
||||
struct com_heap_ptr_deleter {
|
||||
void operator()(void * ptr) const noexcept { CoTaskMemFree(ptr); }
|
||||
};
|
||||
|
@ -103,7 +110,9 @@ struct com_heap_ptr_deleter {
|
|||
template <typename T>
|
||||
using com_heap_ptr = std::unique_ptr<T, com_heap_ptr_deleter>;
|
||||
|
||||
template <typename T, size_t N> constexpr size_t ARRAY_LENGTH(T (&)[N])
|
||||
template <typename T, size_t N>
|
||||
constexpr size_t
|
||||
ARRAY_LENGTH(T (&)[N])
|
||||
{
|
||||
return N;
|
||||
}
|
||||
|
@ -181,6 +190,20 @@ private:
|
|||
T * ptr = nullptr;
|
||||
};
|
||||
|
||||
LONG
|
||||
wasapi_stream_add_ref(cubeb_stream * stm);
|
||||
LONG
|
||||
wasapi_stream_release(cubeb_stream * stm);
|
||||
|
||||
struct auto_stream_ref {
|
||||
auto_stream_ref(cubeb_stream * stm_) : stm(stm_)
|
||||
{
|
||||
wasapi_stream_add_ref(stm);
|
||||
}
|
||||
~auto_stream_ref() { wasapi_stream_release(stm); }
|
||||
cubeb_stream * stm;
|
||||
};
|
||||
|
||||
extern cubeb_ops const wasapi_ops;
|
||||
|
||||
static com_heap_ptr<wchar_t>
|
||||
|
@ -223,6 +246,11 @@ private:
|
|||
com_heap_ptr<wchar_t> capture_comms_id;
|
||||
};
|
||||
|
||||
struct AutoRegisterThread {
|
||||
AutoRegisterThread(const char * name) { CUBEB_REGISTER_THREAD(name); }
|
||||
~AutoRegisterThread() { CUBEB_UNREGISTER_THREAD(); }
|
||||
};
|
||||
|
||||
int
|
||||
wasapi_stream_stop(cubeb_stream * stm);
|
||||
int
|
||||
|
@ -365,8 +393,8 @@ struct cubeb_stream {
|
|||
com_ptr<IAudioClient> input_client;
|
||||
/* Interface to use the event driven capture interface */
|
||||
com_ptr<IAudioCaptureClient> capture_client;
|
||||
/* This event is set by the stream_stop and stream_destroy
|
||||
function, so the render loop can exit properly. */
|
||||
/* This event is set by the stream_destroy function, so the render loop can
|
||||
exit properly. */
|
||||
HANDLE shutdown_event = 0;
|
||||
/* Set by OnDefaultDeviceChanged when a stream reconfiguration is required.
|
||||
The reconfiguration is handled by the render loop thread. */
|
||||
|
@ -410,17 +438,23 @@ struct cubeb_stream {
|
|||
float volume = 1.0;
|
||||
/* True if the stream is draining. */
|
||||
bool draining = false;
|
||||
/* If the render thread fails to stop, this is set to true and ownership of
|
||||
* the stm is "leaked" to the render thread for later cleanup. */
|
||||
std::atomic<bool> emergency_bailout{false};
|
||||
/* This needs an active audio input stream to be known, and is updated in the
|
||||
* first audio input callback. */
|
||||
std::atomic<int64_t> input_latency_hns{LATENCY_NOT_AVAILABLE_YET};
|
||||
|
||||
/* Those attributes count the number of frames requested (resp. received) by
|
||||
the OS, to be able to detect drifts. This is only used for logging for now. */
|
||||
size_t total_input_frames = 0;
|
||||
size_t total_output_frames = 0;
|
||||
/* This is set by the render loop thread once it has obtained a reference to
|
||||
* COM and this stream object. */
|
||||
HANDLE thread_ready_event = 0;
|
||||
/* Keep a ref count on this stream object. After both stream_destroy has been
|
||||
* called and the render loop thread has exited, destroy this stream object.
|
||||
*/
|
||||
LONG ref_count = 0;
|
||||
|
||||
/* True if the stream is active, false if inactive. */
|
||||
bool active = false;
|
||||
};
|
||||
|
||||
class monitor_device_notifications {
|
||||
|
@ -463,6 +497,7 @@ public:
|
|||
private:
|
||||
static unsigned int __stdcall thread_proc(LPVOID args)
|
||||
{
|
||||
AutoRegisterThread raii("WASAPI device notification thread");
|
||||
XASSERT(args);
|
||||
auto mdn = static_cast<monitor_device_notifications *>(args);
|
||||
mdn->notification_thread_loop();
|
||||
|
@ -689,7 +724,8 @@ public:
|
|||
}
|
||||
|
||||
wasapi_endpoint_notification_client(HANDLE event, ERole role)
|
||||
: ref_count(1), reconfigure_event(event), role(role)
|
||||
: ref_count(1), reconfigure_event(event), role(role),
|
||||
last_device_change(timeGetTime())
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -698,17 +734,32 @@ public:
|
|||
HRESULT STDMETHODCALLTYPE OnDefaultDeviceChanged(EDataFlow flow, ERole role,
|
||||
LPCWSTR device_id)
|
||||
{
|
||||
LOG("endpoint: Audio device default changed.");
|
||||
LOG("endpoint: Audio device default changed flow=%d role=%d "
|
||||
"new_device_id=%ws.",
|
||||
flow, role, device_id);
|
||||
|
||||
/* we only support a single stream type for now. */
|
||||
if (flow != eRender && role != this->role) {
|
||||
if (flow != eRender || role != this->role) {
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
BOOL ok = SetEvent(reconfigure_event);
|
||||
if (!ok) {
|
||||
LOG("endpoint: SetEvent on reconfigure_event failed: %lx",
|
||||
GetLastError());
|
||||
DWORD last_change_ms = timeGetTime() - last_device_change;
|
||||
bool same_device = default_device_id && device_id &&
|
||||
wcscmp(default_device_id.get(), device_id) == 0;
|
||||
LOG("endpoint: Audio device default changed last_change=%u same_device=%d",
|
||||
last_change_ms, same_device);
|
||||
if (last_change_ms > DEVICE_CHANGE_DEBOUNCE_MS || !same_device) {
|
||||
if (device_id) {
|
||||
default_device_id.reset(_wcsdup(device_id));
|
||||
} else {
|
||||
default_device_id.reset();
|
||||
}
|
||||
BOOL ok = SetEvent(reconfigure_event);
|
||||
LOG("endpoint: Audio device default changed: trigger reconfig");
|
||||
if (!ok) {
|
||||
LOG("endpoint: SetEvent on reconfigure_event failed: %lx",
|
||||
GetLastError());
|
||||
}
|
||||
}
|
||||
|
||||
return S_OK;
|
||||
|
@ -747,6 +798,8 @@ private:
|
|||
LONG ref_count;
|
||||
HANDLE reconfigure_event;
|
||||
ERole role;
|
||||
std::unique_ptr<const wchar_t[]> default_device_id;
|
||||
DWORD last_device_change;
|
||||
};
|
||||
|
||||
namespace {
|
||||
|
@ -756,9 +809,6 @@ wasapi_data_callback(cubeb_stream * stm, void * user_ptr,
|
|||
void const * input_buffer, void * output_buffer,
|
||||
long nframes)
|
||||
{
|
||||
if (stm->emergency_bailout) {
|
||||
return CUBEB_ERROR;
|
||||
}
|
||||
return stm->data_callback(stm, user_ptr, input_buffer, output_buffer,
|
||||
nframes);
|
||||
}
|
||||
|
@ -766,9 +816,6 @@ wasapi_data_callback(cubeb_stream * stm, void * user_ptr,
|
|||
void
|
||||
wasapi_state_callback(cubeb_stream * stm, void * user_ptr, cubeb_state state)
|
||||
{
|
||||
if (stm->emergency_bailout) {
|
||||
return;
|
||||
}
|
||||
return stm->state_callback(stm, user_ptr, state);
|
||||
}
|
||||
|
||||
|
@ -1303,8 +1350,10 @@ refill_callback_output(cubeb_stream * stm)
|
|||
|
||||
long got = refill(stm, nullptr, 0, output_buffer, output_frames);
|
||||
|
||||
ALOGV("Output callback: output frames requested: %Iu, got %ld", output_frames,
|
||||
got);
|
||||
if (got != output_frames) {
|
||||
ALOGV("Output callback: output frames requested: %Iu, got %ld", output_frames,
|
||||
got);
|
||||
}
|
||||
if (got < 0) {
|
||||
return false;
|
||||
}
|
||||
|
@ -1322,29 +1371,12 @@ refill_callback_output(cubeb_stream * stm)
|
|||
void
|
||||
wasapi_stream_destroy(cubeb_stream * stm);
|
||||
|
||||
static void
|
||||
handle_emergency_bailout(cubeb_stream * stm)
|
||||
{
|
||||
if (stm->emergency_bailout) {
|
||||
CloseHandle(stm->thread);
|
||||
stm->thread = NULL;
|
||||
CloseHandle(stm->shutdown_event);
|
||||
stm->shutdown_event = 0;
|
||||
wasapi_stream_destroy(stm);
|
||||
_endthreadex(0);
|
||||
}
|
||||
}
|
||||
|
||||
static unsigned int __stdcall wasapi_stream_render_loop(LPVOID stream)
|
||||
{
|
||||
AutoRegisterThread raii("cubeb rendering thread");
|
||||
cubeb_stream * stm = static_cast<cubeb_stream *>(stream);
|
||||
|
||||
bool is_playing = true;
|
||||
HANDLE wait_array[4] = {stm->shutdown_event, stm->reconfigure_event,
|
||||
stm->refill_event, stm->input_available_event};
|
||||
HANDLE mmcss_handle = NULL;
|
||||
HRESULT hr = 0;
|
||||
DWORD mmcss_task_index = 0;
|
||||
auto_stream_ref stream_ref(stm);
|
||||
struct auto_com {
|
||||
auto_com()
|
||||
{
|
||||
|
@ -1354,6 +1386,21 @@ static unsigned int __stdcall wasapi_stream_render_loop(LPVOID stream)
|
|||
~auto_com() { CoUninitialize(); }
|
||||
} com;
|
||||
|
||||
bool is_playing = true;
|
||||
HANDLE wait_array[4] = {stm->shutdown_event, stm->reconfigure_event,
|
||||
stm->refill_event, stm->input_available_event};
|
||||
HANDLE mmcss_handle = NULL;
|
||||
HRESULT hr = 0;
|
||||
DWORD mmcss_task_index = 0;
|
||||
|
||||
// Signal wasapi_stream_start that we've initialized COM and incremented
|
||||
// the stream's ref_count.
|
||||
BOOL ok = SetEvent(stm->thread_ready_event);
|
||||
if (!ok) {
|
||||
LOG("thread_ready SetEvent failed: %lx", GetLastError());
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* We could consider using "Pro Audio" here for WebAudio and
|
||||
maybe WebRTC. */
|
||||
mmcss_handle = AvSetMmThreadCharacteristicsA("Audio", &mmcss_task_index);
|
||||
|
@ -1363,20 +1410,9 @@ static unsigned int __stdcall wasapi_stream_render_loop(LPVOID stream)
|
|||
GetLastError());
|
||||
}
|
||||
|
||||
/* WaitForMultipleObjects timeout can trigger in cases where we don't want to
|
||||
treat it as a timeout, such as across a system sleep/wake cycle. Trigger
|
||||
the timeout error handling only when the timeout_limit is reached, which is
|
||||
reset on each successful loop. */
|
||||
unsigned timeout_count = 0;
|
||||
const unsigned timeout_limit = 3;
|
||||
while (is_playing) {
|
||||
handle_emergency_bailout(stm);
|
||||
DWORD waitResult = WaitForMultipleObjects(ARRAY_LENGTH(wait_array),
|
||||
wait_array, FALSE, 1000);
|
||||
handle_emergency_bailout(stm);
|
||||
if (waitResult != WAIT_TIMEOUT) {
|
||||
timeout_count = 0;
|
||||
}
|
||||
wait_array, FALSE, INFINITE);
|
||||
switch (waitResult) {
|
||||
case WAIT_OBJECT_0: { /* shutdown */
|
||||
is_playing = false;
|
||||
|
@ -1388,36 +1424,40 @@ static unsigned int __stdcall wasapi_stream_render_loop(LPVOID stream)
|
|||
continue;
|
||||
}
|
||||
case WAIT_OBJECT_0 + 1: { /* reconfigure */
|
||||
auto_lock lock(stm->stream_reset_lock);
|
||||
if (!stm->active) {
|
||||
/* Avoid reconfiguring, stream start will handle it. */
|
||||
LOG("Stream is not active, ignoring reconfigure.");
|
||||
continue;
|
||||
}
|
||||
XASSERT(stm->output_client || stm->input_client);
|
||||
LOG("Reconfiguring the stream");
|
||||
/* Close the stream */
|
||||
bool was_running = false;
|
||||
if (stm->output_client) {
|
||||
stm->output_client->Stop();
|
||||
was_running = stm->output_client->Stop() == S_OK;
|
||||
LOG("Output stopped.");
|
||||
}
|
||||
if (stm->input_client) {
|
||||
stm->input_client->Stop();
|
||||
was_running = stm->input_client->Stop() == S_OK;
|
||||
LOG("Input stopped.");
|
||||
}
|
||||
{
|
||||
auto_lock lock(stm->stream_reset_lock);
|
||||
close_wasapi_stream(stm);
|
||||
LOG("Stream closed.");
|
||||
/* Reopen a stream and start it immediately. This will automatically
|
||||
pick the new default device for this role. */
|
||||
int r = setup_wasapi_stream(stm);
|
||||
if (r != CUBEB_OK) {
|
||||
LOG("Error setting up the stream during reconfigure.");
|
||||
/* Don't destroy the stream here, since we expect the caller to do
|
||||
so after the error has propagated via the state callback. */
|
||||
is_playing = false;
|
||||
hr = E_FAIL;
|
||||
continue;
|
||||
}
|
||||
LOG("Stream setup successfuly.");
|
||||
close_wasapi_stream(stm);
|
||||
LOG("Stream closed.");
|
||||
/* Reopen a stream and start it immediately. This will automatically
|
||||
pick the new default device for this role. */
|
||||
int r = setup_wasapi_stream(stm);
|
||||
if (r != CUBEB_OK) {
|
||||
LOG("Error setting up the stream during reconfigure.");
|
||||
/* Don't destroy the stream here, since we expect the caller to do
|
||||
so after the error has propagated via the state callback. */
|
||||
is_playing = false;
|
||||
hr = E_FAIL;
|
||||
continue;
|
||||
}
|
||||
LOG("Stream setup successfuly.");
|
||||
XASSERT(stm->output_client || stm->input_client);
|
||||
if (stm->output_client) {
|
||||
if (was_running && stm->output_client) {
|
||||
hr = stm->output_client->Start();
|
||||
if (FAILED(hr)) {
|
||||
LOG("Error starting output after reconfigure, error: %lx", hr);
|
||||
|
@ -1426,7 +1466,7 @@ static unsigned int __stdcall wasapi_stream_render_loop(LPVOID stream)
|
|||
}
|
||||
LOG("Output started after reconfigure.");
|
||||
}
|
||||
if (stm->input_client) {
|
||||
if (was_running && stm->input_client) {
|
||||
hr = stm->input_client->Start();
|
||||
if (FAILED(hr)) {
|
||||
LOG("Error starting input after reconfiguring, error: %lx", hr);
|
||||
|
@ -1455,14 +1495,6 @@ static unsigned int __stdcall wasapi_stream_render_loop(LPVOID stream)
|
|||
|
||||
break;
|
||||
}
|
||||
case WAIT_TIMEOUT:
|
||||
XASSERT(stm->shutdown_event == wait_array[0]);
|
||||
if (++timeout_count >= timeout_limit) {
|
||||
LOG("Render loop reached the timeout limit.");
|
||||
is_playing = false;
|
||||
hr = E_FAIL;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
LOG("case %lu not handled in render loop.", waitResult);
|
||||
XASSERT(false);
|
||||
|
@ -1482,8 +1514,6 @@ static unsigned int __stdcall wasapi_stream_render_loop(LPVOID stream)
|
|||
AvRevertMmThreadCharacteristics(mmcss_handle);
|
||||
}
|
||||
|
||||
handle_emergency_bailout(stm);
|
||||
|
||||
if (FAILED(hr)) {
|
||||
wasapi_state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR);
|
||||
}
|
||||
|
@ -1739,24 +1769,16 @@ namespace {
|
|||
enum ShutdownPhase { OnStop, OnDestroy };
|
||||
|
||||
bool
|
||||
stop_and_join_render_thread(cubeb_stream * stm, ShutdownPhase phase)
|
||||
stop_and_join_render_thread(cubeb_stream * stm)
|
||||
{
|
||||
// Only safe to transfer `stm` ownership to the render thread when
|
||||
// the stream is being destroyed by the caller.
|
||||
bool bailout = phase == OnDestroy;
|
||||
|
||||
LOG("%p: Stop and join render thread: %p (%d), phase=%d", stm, stm->thread,
|
||||
stm->emergency_bailout.load(), static_cast<int>(phase));
|
||||
LOG("%p: Stop and join render thread: %p", stm, stm->thread);
|
||||
if (!stm->thread) {
|
||||
return true;
|
||||
}
|
||||
|
||||
XASSERT(!stm->emergency_bailout);
|
||||
|
||||
BOOL ok = SetEvent(stm->shutdown_event);
|
||||
if (!ok) {
|
||||
LOG("stop_and_join_render_thread: SetEvent failed: %lx", GetLastError());
|
||||
stm->emergency_bailout = bailout;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -1774,31 +1796,23 @@ stop_and_join_render_thread(cubeb_stream * stm, ShutdownPhase phase)
|
|||
LOG("stop_and_join_render_thread: WaitForSingleObject on thread failed: "
|
||||
"%lx, %lx",
|
||||
r, GetLastError());
|
||||
stm->emergency_bailout = bailout;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Only attempt to close and null out the thread and event if the
|
||||
// WaitForSingleObject above succeeded.
|
||||
LOG("stop_and_join_render_thread: Closing thread.");
|
||||
CloseHandle(stm->thread);
|
||||
stm->thread = NULL;
|
||||
|
||||
CloseHandle(stm->shutdown_event);
|
||||
stm->shutdown_event = 0;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
wasapi_destroy(cubeb * context)
|
||||
{
|
||||
auto_lock lock(context->lock);
|
||||
XASSERT(!context->device_collection_enumerator &&
|
||||
!context->collection_notification_client);
|
||||
{
|
||||
auto_lock lock(context->lock);
|
||||
XASSERT(!context->device_collection_enumerator &&
|
||||
!context->collection_notification_client);
|
||||
|
||||
if (context->device_ids) {
|
||||
cubeb_strings_destroy(context->device_ids);
|
||||
if (context->device_ids) {
|
||||
cubeb_strings_destroy(context->device_ids);
|
||||
}
|
||||
}
|
||||
|
||||
delete context;
|
||||
|
@ -2469,8 +2483,8 @@ setup_wasapi_stream(cubeb_stream * stm)
|
|||
std::unique_ptr<const wchar_t[]> selected_output_device_id;
|
||||
if (stm->output_device_id) {
|
||||
if (std::unique_ptr<wchar_t[]> tmp =
|
||||
move(copy_wide_string(stm->output_device_id.get()))) {
|
||||
selected_output_device_id = move(tmp);
|
||||
copy_wide_string(stm->output_device_id.get())) {
|
||||
selected_output_device_id = std::move(tmp);
|
||||
} else {
|
||||
LOG("Failed to copy output device identifier.");
|
||||
return CUBEB_ERROR;
|
||||
|
@ -2512,7 +2526,7 @@ setup_wasapi_stream(cubeb_stream * stm)
|
|||
cubeb_devid matched = wasapi_find_bt_handsfree_output_device(stm);
|
||||
if (matched) {
|
||||
selected_output_device_id =
|
||||
move(utf8_to_wstr(reinterpret_cast<char const *>(matched)));
|
||||
utf8_to_wstr(reinterpret_cast<char const *>(matched));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2528,9 +2542,9 @@ setup_wasapi_stream(cubeb_stream * stm)
|
|||
stm->output_stream_params.layout = stm->input_stream_params.layout;
|
||||
if (stm->input_device_id) {
|
||||
if (std::unique_ptr<wchar_t[]> tmp =
|
||||
move(copy_wide_string(stm->input_device_id.get()))) {
|
||||
copy_wide_string(stm->input_device_id.get())) {
|
||||
XASSERT(!selected_output_device_id);
|
||||
selected_output_device_id = move(tmp);
|
||||
selected_output_device_id = std::move(tmp);
|
||||
} else {
|
||||
LOG("Failed to copy device identifier while copying input stream "
|
||||
"configuration to output stream configuration to drive loopback.");
|
||||
|
@ -2690,8 +2704,8 @@ wasapi_stream_init(cubeb * context, cubeb_stream ** stream,
|
|||
return CUBEB_ERROR_INVALID_FORMAT;
|
||||
}
|
||||
|
||||
std::unique_ptr<cubeb_stream, decltype(&wasapi_stream_destroy)> stm(
|
||||
new cubeb_stream(), wasapi_stream_destroy);
|
||||
cubeb_stream * stm = new cubeb_stream();
|
||||
auto_stream_ref stream_ref(stm);
|
||||
|
||||
stm->context = context;
|
||||
stm->data_callback = data_callback;
|
||||
|
@ -2763,12 +2777,24 @@ wasapi_stream_init(cubeb * context, cubeb_stream ** stream,
|
|||
return CUBEB_ERROR;
|
||||
}
|
||||
|
||||
stm->shutdown_event = CreateEvent(NULL, 0, 0, NULL);
|
||||
if (!stm->shutdown_event) {
|
||||
LOG("Can't create the shutdown event, error: %lx", GetLastError());
|
||||
return CUBEB_ERROR;
|
||||
}
|
||||
|
||||
stm->thread_ready_event = CreateEvent(NULL, 0, 0, NULL);
|
||||
if (!stm->thread_ready_event) {
|
||||
LOG("Can't create the thread ready event, error: %lx", GetLastError());
|
||||
return CUBEB_ERROR;
|
||||
}
|
||||
|
||||
{
|
||||
/* Locking here is not strictly necessary, because we don't have a
|
||||
notification client that can reset the stream yet, but it lets us
|
||||
assert that the lock is held in the function. */
|
||||
auto_lock lock(stm->stream_reset_lock);
|
||||
rv = setup_wasapi_stream(stm.get());
|
||||
rv = setup_wasapi_stream(stm);
|
||||
}
|
||||
if (rv != CUBEB_OK) {
|
||||
return rv;
|
||||
|
@ -2783,7 +2809,7 @@ wasapi_stream_init(cubeb * context, cubeb_stream ** stream,
|
|||
!(output_stream_params->prefs &
|
||||
CUBEB_STREAM_PREF_DISABLE_DEVICE_SWITCHING))) {
|
||||
LOG("Follow the system default input or/and output devices");
|
||||
HRESULT hr = register_notification_client(stm.get());
|
||||
HRESULT hr = register_notification_client(stm);
|
||||
if (FAILED(hr)) {
|
||||
/* this is not fatal, we can still play audio, but we won't be able
|
||||
to keep using the default audio endpoint if it changes. */
|
||||
|
@ -2791,9 +2817,25 @@ wasapi_stream_init(cubeb * context, cubeb_stream ** stream,
|
|||
}
|
||||
}
|
||||
|
||||
*stream = stm.release();
|
||||
stm->thread =
|
||||
(HANDLE)_beginthreadex(NULL, 512 * 1024, wasapi_stream_render_loop, stm,
|
||||
STACK_SIZE_PARAM_IS_A_RESERVATION, NULL);
|
||||
if (stm->thread == NULL) {
|
||||
LOG("could not create WASAPI render thread.");
|
||||
return CUBEB_ERROR;
|
||||
}
|
||||
|
||||
LOG("Stream init succesfull (%p)", *stream);
|
||||
// Wait for the wasapi_stream_render_loop thread to signal that COM has been
|
||||
// initialized and the stream's ref_count has been incremented.
|
||||
hr = WaitForSingleObject(stm->thread_ready_event, INFINITE);
|
||||
XASSERT(hr == WAIT_OBJECT_0);
|
||||
CloseHandle(stm->thread_ready_event);
|
||||
stm->thread_ready_event = 0;
|
||||
|
||||
wasapi_stream_add_ref(stm);
|
||||
*stream = stm;
|
||||
|
||||
LOG("Stream init successful (%p)", *stream);
|
||||
return CUBEB_OK;
|
||||
}
|
||||
|
||||
|
@ -2804,20 +2846,18 @@ close_wasapi_stream(cubeb_stream * stm)
|
|||
|
||||
stm->stream_reset_lock.assert_current_thread_owns();
|
||||
|
||||
stm->output_client = nullptr;
|
||||
stm->render_client = nullptr;
|
||||
|
||||
stm->input_client = nullptr;
|
||||
stm->capture_client = nullptr;
|
||||
|
||||
stm->output_device = nullptr;
|
||||
stm->input_device = nullptr;
|
||||
|
||||
#ifdef CUBEB_WASAPI_USE_IAUDIOSTREAMVOLUME
|
||||
stm->audio_stream_volume = nullptr;
|
||||
#endif
|
||||
|
||||
stm->audio_clock = nullptr;
|
||||
stm->render_client = nullptr;
|
||||
stm->output_client = nullptr;
|
||||
stm->output_device = nullptr;
|
||||
|
||||
stm->capture_client = nullptr;
|
||||
stm->input_client = nullptr;
|
||||
stm->input_device = nullptr;
|
||||
|
||||
stm->total_frames_written += static_cast<UINT64>(
|
||||
round(stm->frames_written *
|
||||
stream_to_mix_samplerate_ratio(stm->output_stream_params,
|
||||
|
@ -2833,32 +2873,59 @@ close_wasapi_stream(cubeb_stream * stm)
|
|||
}
|
||||
}
|
||||
|
||||
LONG
|
||||
wasapi_stream_add_ref(cubeb_stream * stm)
|
||||
{
|
||||
XASSERT(stm);
|
||||
LONG result = InterlockedIncrement(&stm->ref_count);
|
||||
LOGV("Stream ref count incremented = %i (%p)", result, stm);
|
||||
return result;
|
||||
}
|
||||
|
||||
LONG
|
||||
wasapi_stream_release(cubeb_stream * stm)
|
||||
{
|
||||
XASSERT(stm);
|
||||
|
||||
LONG result = InterlockedDecrement(&stm->ref_count);
|
||||
LOGV("Stream ref count decremented = %i (%p)", result, stm);
|
||||
if (result == 0) {
|
||||
LOG("Stream ref count hit zero, destroying (%p)", stm);
|
||||
|
||||
if (stm->notification_client) {
|
||||
unregister_notification_client(stm);
|
||||
}
|
||||
|
||||
CloseHandle(stm->shutdown_event);
|
||||
CloseHandle(stm->reconfigure_event);
|
||||
CloseHandle(stm->refill_event);
|
||||
CloseHandle(stm->input_available_event);
|
||||
|
||||
CloseHandle(stm->thread);
|
||||
|
||||
// The variables intialized in wasapi_stream_init,
|
||||
// must be destroyed in wasapi_stream_release.
|
||||
stm->linear_input_buffer.reset();
|
||||
|
||||
{
|
||||
auto_lock lock(stm->stream_reset_lock);
|
||||
close_wasapi_stream(stm);
|
||||
}
|
||||
|
||||
delete stm;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void
|
||||
wasapi_stream_destroy(cubeb_stream * stm)
|
||||
{
|
||||
XASSERT(stm);
|
||||
LOG("Stream destroy (%p)", stm);
|
||||
LOG("Stream destroy called, decrementing ref count (%p)", stm);
|
||||
|
||||
if (!stop_and_join_render_thread(stm, OnDestroy)) {
|
||||
// Emergency bailout: render thread becomes responsible for calling
|
||||
// wasapi_stream_destroy.
|
||||
return;
|
||||
}
|
||||
|
||||
if (stm->notification_client) {
|
||||
unregister_notification_client(stm);
|
||||
}
|
||||
|
||||
{
|
||||
auto_lock lock(stm->stream_reset_lock);
|
||||
close_wasapi_stream(stm);
|
||||
}
|
||||
|
||||
CloseHandle(stm->reconfigure_event);
|
||||
CloseHandle(stm->refill_event);
|
||||
CloseHandle(stm->input_available_event);
|
||||
|
||||
delete stm;
|
||||
stop_and_join_render_thread(stm);
|
||||
wasapi_stream_release(stm);
|
||||
}
|
||||
|
||||
enum StreamDirection { OUTPUT, INPUT };
|
||||
|
@ -2866,6 +2933,7 @@ enum StreamDirection { OUTPUT, INPUT };
|
|||
int
|
||||
stream_start_one_side(cubeb_stream * stm, StreamDirection dir)
|
||||
{
|
||||
XASSERT(stm);
|
||||
XASSERT((dir == OUTPUT && stm->output_client) ||
|
||||
(dir == INPUT && stm->input_client));
|
||||
|
||||
|
@ -2909,7 +2977,7 @@ wasapi_stream_start(cubeb_stream * stm)
|
|||
{
|
||||
auto_lock lock(stm->stream_reset_lock);
|
||||
|
||||
XASSERT(stm && !stm->thread && !stm->shutdown_event);
|
||||
XASSERT(stm);
|
||||
XASSERT(stm->output_client || stm->input_client);
|
||||
|
||||
if (stm->output_client) {
|
||||
|
@ -2926,24 +2994,9 @@ wasapi_stream_start(cubeb_stream * stm)
|
|||
}
|
||||
}
|
||||
|
||||
stm->shutdown_event = CreateEvent(NULL, 0, 0, NULL);
|
||||
if (!stm->shutdown_event) {
|
||||
LOG("Can't create the shutdown event, error: %lx", GetLastError());
|
||||
return CUBEB_ERROR;
|
||||
}
|
||||
stm->active = true;
|
||||
|
||||
cubeb_async_log_reset_threads();
|
||||
stm->thread =
|
||||
(HANDLE)_beginthreadex(NULL, 512 * 1024, wasapi_stream_render_loop, stm,
|
||||
STACK_SIZE_PARAM_IS_A_RESERVATION, NULL);
|
||||
if (stm->thread == NULL) {
|
||||
LOG("could not create WASAPI render thread.");
|
||||
CloseHandle(stm->shutdown_event);
|
||||
stm->shutdown_event = 0;
|
||||
return CUBEB_ERROR;
|
||||
}
|
||||
|
||||
wasapi_state_callback(stm, stm->user_ptr, CUBEB_STATE_STARTED);
|
||||
stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STARTED);
|
||||
|
||||
return CUBEB_OK;
|
||||
}
|
||||
|
@ -2973,13 +3026,9 @@ wasapi_stream_stop(cubeb_stream * stm)
|
|||
}
|
||||
}
|
||||
|
||||
wasapi_state_callback(stm, stm->user_ptr, CUBEB_STATE_STOPPED);
|
||||
}
|
||||
stm->active = false;
|
||||
|
||||
if (!stop_and_join_render_thread(stm, OnStop)) {
|
||||
// If we could not join the thread, put the stream in error.
|
||||
wasapi_state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR);
|
||||
return CUBEB_ERROR;
|
||||
wasapi_state_callback(stm, stm->user_ptr, CUBEB_STATE_STOPPED);
|
||||
}
|
||||
|
||||
return CUBEB_OK;
|
||||
|
@ -3114,8 +3163,9 @@ wstr_to_utf8(LPCWSTR str)
|
|||
return ret;
|
||||
}
|
||||
|
||||
static std::unique_ptr<wchar_t const []>
|
||||
utf8_to_wstr(char const * str) {
|
||||
static std::unique_ptr<wchar_t const[]>
|
||||
utf8_to_wstr(char const * str)
|
||||
{
|
||||
int size = ::MultiByteToWideChar(CP_UTF8, 0, str, -1, nullptr, 0);
|
||||
if (size <= 0) {
|
||||
return nullptr;
|
||||
|
@ -3126,8 +3176,8 @@ utf8_to_wstr(char const * str) {
|
|||
return ret;
|
||||
}
|
||||
|
||||
static com_ptr<IMMDevice> wasapi_get_device_node(
|
||||
IMMDeviceEnumerator * enumerator, IMMDevice * dev)
|
||||
static com_ptr<IMMDevice>
|
||||
wasapi_get_device_node(IMMDeviceEnumerator * enumerator, IMMDevice * dev)
|
||||
{
|
||||
com_ptr<IMMDevice> ret;
|
||||
com_ptr<IDeviceTopology> devtopo;
|
||||
|
|
|
@ -105,10 +105,13 @@ struct cubeb_stream {
|
|||
int free_buffers;
|
||||
int shutdown;
|
||||
int draining;
|
||||
int error;
|
||||
HANDLE event;
|
||||
HWAVEOUT waveout;
|
||||
CRITICAL_SECTION lock;
|
||||
uint64_t written;
|
||||
/* number of frames written during preroll */
|
||||
uint64_t position_base;
|
||||
float soft_volume;
|
||||
/* For position wrap-around handling: */
|
||||
size_t frame_size;
|
||||
|
@ -150,6 +153,14 @@ winmm_get_next_buffer(cubeb_stream * stm)
|
|||
return hdr;
|
||||
}
|
||||
|
||||
static long
|
||||
preroll_callback(cubeb_stream * stream, void * user, const void * inputbuffer,
|
||||
void * outputbuffer, long nframes)
|
||||
{
|
||||
memset((uint8_t *)outputbuffer, 0, nframes * bytes_per_frame(stream->params));
|
||||
return nframes;
|
||||
}
|
||||
|
||||
static void
|
||||
winmm_refill_stream(cubeb_stream * stm)
|
||||
{
|
||||
|
@ -158,13 +169,20 @@ winmm_refill_stream(cubeb_stream * stm)
|
|||
long wanted;
|
||||
MMRESULT r;
|
||||
|
||||
ALOG("winmm_refill_stream");
|
||||
|
||||
EnterCriticalSection(&stm->lock);
|
||||
if (stm->error) {
|
||||
LeaveCriticalSection(&stm->lock);
|
||||
return;
|
||||
}
|
||||
stm->free_buffers += 1;
|
||||
XASSERT(stm->free_buffers > 0 && stm->free_buffers <= NBUFS);
|
||||
|
||||
if (stm->draining) {
|
||||
LeaveCriticalSection(&stm->lock);
|
||||
if (stm->free_buffers == NBUFS) {
|
||||
ALOG("winmm_refill_stream draining");
|
||||
stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_DRAINED);
|
||||
}
|
||||
SetEvent(stm->event);
|
||||
|
@ -187,9 +205,10 @@ winmm_refill_stream(cubeb_stream * stm)
|
|||
got = stm->data_callback(stm, stm->user_ptr, NULL, hdr->lpData, wanted);
|
||||
EnterCriticalSection(&stm->lock);
|
||||
if (got < 0) {
|
||||
stm->error = 1;
|
||||
LeaveCriticalSection(&stm->lock);
|
||||
/* XXX handle this case */
|
||||
XASSERT(0);
|
||||
SetEvent(stm->event);
|
||||
stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR);
|
||||
return;
|
||||
} else if (got < wanted) {
|
||||
stm->draining = 1;
|
||||
|
@ -224,6 +243,8 @@ winmm_refill_stream(cubeb_stream * stm)
|
|||
return;
|
||||
}
|
||||
|
||||
ALOG("winmm_refill_stream %ld frames", got);
|
||||
|
||||
LeaveCriticalSection(&stm->lock);
|
||||
}
|
||||
|
||||
|
@ -486,7 +507,11 @@ winmm_stream_init(cubeb * context, cubeb_stream ** stream,
|
|||
|
||||
stm->params = *output_stream_params;
|
||||
|
||||
stm->data_callback = data_callback;
|
||||
// Data callback is set to the user-provided data callback after
|
||||
// the initialization and potential preroll callback calls are done, because
|
||||
// cubeb users don't expect the data callback to be called during
|
||||
// initialization.
|
||||
stm->data_callback = preroll_callback;
|
||||
stm->state_callback = state_callback;
|
||||
stm->user_ptr = user_ptr;
|
||||
stm->written = 0;
|
||||
|
@ -553,9 +578,18 @@ winmm_stream_init(cubeb * context, cubeb_stream ** stream,
|
|||
stm->frame_size = bytes_per_frame(stm->params);
|
||||
stm->prev_pos_lo_dword = 0;
|
||||
stm->pos_hi_dword = 0;
|
||||
// Set the user data callback now that preroll has finished.
|
||||
stm->data_callback = data_callback;
|
||||
stm->position_base = 0;
|
||||
|
||||
// Offset the position by the number of frames written during preroll.
|
||||
stm->position_base = stm->written;
|
||||
stm->written = 0;
|
||||
|
||||
*stream = stm;
|
||||
|
||||
LOG("winmm_stream_init OK");
|
||||
|
||||
return CUBEB_OK;
|
||||
}
|
||||
|
||||
|
@ -585,7 +619,7 @@ winmm_stream_destroy(cubeb_stream * stm)
|
|||
LeaveCriticalSection(&stm->lock);
|
||||
|
||||
/* Wait for all blocks to complete. */
|
||||
while (device_valid && enqueued > 0) {
|
||||
while (device_valid && enqueued > 0 && !stm->error) {
|
||||
DWORD rv = WaitForSingleObject(stm->event, INFINITE);
|
||||
XASSERT(rv == WAIT_OBJECT_0);
|
||||
|
||||
|
@ -774,7 +808,17 @@ winmm_stream_get_position(cubeb_stream * stm, uint64_t * position)
|
|||
return CUBEB_ERROR;
|
||||
}
|
||||
|
||||
*position = update_64bit_position(stm, time.u.cb) / stm->frame_size;
|
||||
uint64_t position_not_adjusted =
|
||||
update_64bit_position(stm, time.u.cb) / stm->frame_size;
|
||||
|
||||
// Subtract the number of frames that were written while prerolling, during
|
||||
// initialization.
|
||||
if (position_not_adjusted < stm->position_base) {
|
||||
*position = 0;
|
||||
} else {
|
||||
*position = position_not_adjusted - stm->position_base;
|
||||
}
|
||||
|
||||
LeaveCriticalSection(&stm->lock);
|
||||
|
||||
return CUBEB_OK;
|
||||
|
@ -787,17 +831,12 @@ winmm_stream_get_latency(cubeb_stream * stm, uint32_t * latency)
|
|||
MMTIME time;
|
||||
uint64_t written, position;
|
||||
|
||||
EnterCriticalSection(&stm->lock);
|
||||
/* See the long comment above for why not just use TIME_SAMPLES here. */
|
||||
time.wType = TIME_BYTES;
|
||||
r = waveOutGetPosition(stm->waveout, &time, sizeof(time));
|
||||
|
||||
if (r != MMSYSERR_NOERROR || time.wType != TIME_BYTES) {
|
||||
LeaveCriticalSection(&stm->lock);
|
||||
return CUBEB_ERROR;
|
||||
int rv = winmm_stream_get_position(stm, &position);
|
||||
if (rv != CUBEB_OK) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
position = update_64bit_position(stm, time.u.cb);
|
||||
EnterCriticalSection(&stm->lock);
|
||||
written = stm->written;
|
||||
LeaveCriticalSection(&stm->lock);
|
||||
|
||||
|
|
|
@ -22,7 +22,8 @@
|
|||
"modules/21-libbacktrace.json",
|
||||
{
|
||||
"name": "duckstation",
|
||||
"buildsystem": "cmake",
|
||||
"buildsystem": "cmake-ninja",
|
||||
"no-make-install": true,
|
||||
"build-options": {
|
||||
"strip": false,
|
||||
"no-debuginfo": true,
|
||||
|
|
Loading…
Reference in a new issue