dep/cubeb: Minimize and update to 54217bc

This commit is contained in:
Stenzek 2023-11-24 21:14:39 +10:00
parent 7cc52bba23
commit 1b948aab62
No known key found for this signature in database
34 changed files with 562 additions and 6069 deletions

View File

@ -2,10 +2,8 @@
# - backend selection via command line, rather than simply detecting headers. # - backend selection via command line, rather than simply detecting headers.
cmake_minimum_required(VERSION 3.14 FATAL_ERROR) cmake_minimum_required(VERSION 3.14 FATAL_ERROR)
project(cubeb project(cubeb C CXX)
VERSION 0.0.0)
option(BUILD_RUST_LIBS "Build rust backends" OFF)
option(LAZY_LOAD_LIBS "Lazily load shared libraries" ON) option(LAZY_LOAD_LIBS "Lazily load shared libraries" ON)
if(NOT CMAKE_BUILD_TYPE) if(NOT CMAKE_BUILD_TYPE)
@ -14,25 +12,17 @@ if(NOT CMAKE_BUILD_TYPE)
endif() endif()
set(CMAKE_C_STANDARD 99) set(CMAKE_C_STANDARD 99)
set(CMAKE_CXX_STANDARD 11) set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_EXPORT_COMPILE_COMMANDS 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_CXX_WARNING_LEVEL 4) set(CMAKE_CXX_WARNING_LEVEL 4)
if(NOT MSVC) if(NOT MSVC)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra -Wno-unused-parameter") set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra -Wno-unused-parameter")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Wno-unused-parameter -fno-exceptions -fno-rtti") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Wno-unused-parameter -fno-exceptions -fno-rtti")
else() else()
string(REPLACE "/GR" "" CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS}) # Disable RTTI string(REPLACE "/GR" "" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}") # Disable RTTI
string(REPLACE "/EHsc" "" CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS}) # Disable Exceptions string(REPLACE "/EHsc" "" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}") # Disable Exceptions
endif() endif()
add_library(cubeb add_library(cubeb
@ -46,43 +36,15 @@ add_library(cubeb
target_include_directories(cubeb target_include_directories(cubeb
PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include> $<INSTALL_INTERFACE:include> PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include> $<INSTALL_INTERFACE:include>
) )
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) add_library(speex OBJECT subprojects/speex/resample.c)
set_target_properties(speex PROPERTIES POSITION_INDEPENDENT_CODE TRUE) set_target_properties(speex PROPERTIES POSITION_INDEPENDENT_CODE TRUE)
target_include_directories(speex INTERFACE subprojects) target_include_directories(speex INTERFACE subprojects)
target_compile_definitions(speex PUBLIC target_compile_definitions(speex PUBLIC
OUTSIDE_SPEEX OUTSIDE_SPEEX
FLOATING_POINT FLOATING_POINT
EXPORT= EXPORT=
RANDOM_PREFIX=speex RANDOM_PREFIX=speex
) )
# $<BUILD_INTERFACE:> required because of https://gitlab.kitware.com/cmake/cmake/-/issues/15415 # $<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) target_link_libraries(cubeb PRIVATE Threads::Threads)
if(LAZY_LOAD_LIBS) if(LAZY_LOAD_LIBS)
check_include_files(pulse/pulseaudio.h USE_PULSE) if(NOT APPLE AND NOT WIN32)
check_include_files(alsa/asoundlib.h USE_ALSA) # Skip checks on MacOS because it takes ages in XCode.
check_include_files(jack/jack.h USE_JACK) check_include_files(pulse/pulseaudio.h USE_PULSE)
check_include_files(sndio.h USE_SNDIO) check_include_files(alsa/asoundlib.h USE_ALSA)
check_include_files(aaudio/AAudio.h USE_AAUDIO) 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) if(USE_PULSE OR USE_ALSA OR USE_JACK OR USE_SNDIO)
target_link_libraries(cubeb PRIVATE ${CMAKE_DL_LIBS}) target_link_libraries(cubeb PRIVATE ${CMAKE_DL_LIBS})
endif()
endif() endif()
else() elseif(NOT APPLE AND NOT WIN32)
find_package(PkgConfig REQUIRED) find_package(PkgConfig REQUIRED)
@ -136,12 +100,6 @@ else()
target_compile_definitions(cubeb PRIVATE DISABLE_LIBSNDIO_DLOPEN) target_compile_definitions(cubeb PRIVATE DISABLE_LIBSNDIO_DLOPEN)
target_link_libraries(cubeb PRIVATE sndio) target_link_libraries(cubeb PRIVATE sndio)
endif() 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() endif()
if(USE_PULSE) if(USE_PULSE)
@ -164,138 +122,57 @@ if(USE_SNDIO)
target_compile_definitions(cubeb PRIVATE USE_SNDIO) target_compile_definitions(cubeb PRIVATE USE_SNDIO)
endif() endif()
if(USE_AAUDIO) if(APPLE)
target_sources(cubeb PRIVATE src/cubeb_aaudio.cpp) check_include_files(AudioUnit/AudioUnit.h USE_AUDIOUNIT)
target_compile_definitions(cubeb PRIVATE USE_AAUDIO) if(USE_AUDIOUNIT)
target_sources(cubeb PRIVATE
# set this definition to enable low latency mode. Possibly bad for battery src/cubeb_audiounit.cpp
target_compile_definitions(cubeb PRIVATE CUBEB_AAUDIO_LOW_LATENCY) src/cubeb_osx_run_loop.cpp)
target_compile_definitions(cubeb PRIVATE USE_AUDIOUNIT)
# set this definition to enable power saving mode. Possibly resulting target_link_libraries(cubeb PRIVATE "-framework AudioUnit" "-framework CoreAudio" "-framework CoreServices")
# 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()
endif() endif()
endif() endif()
check_include_files(android/log.h USE_AUDIOTRACK) if(WIN32)
if(USE_AUDIOTRACK) check_include_files(audioclient.h USE_WASAPI)
target_sources(cubeb PRIVATE if(USE_WASAPI)
src/cubeb_audiotrack.c) target_sources(cubeb PRIVATE
target_compile_definitions(cubeb PRIVATE USE_AUDIOTRACK) src/cubeb_wasapi.cpp)
target_link_libraries(cubeb PRIVATE log) 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() endif()
check_include_files(sys/audioio.h USE_SUN) if(NOT WIN32 AND NOT APPLE)
if(USE_SUN) check_include_files(sys/soundcard.h HAVE_SYS_SOUNDCARD_H)
target_sources(cubeb PRIVATE if(HAVE_SYS_SOUNDCARD_H)
src/cubeb_sun.c) try_compile(USE_OSS "${PROJECT_BINARY_DIR}/compile_tests"
target_compile_definitions(cubeb PRIVATE USE_SUN) ${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() 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()

View File

@ -1,4 +0,0 @@
@PACKAGE_INIT@
include("${CMAKE_CURRENT_LIST_DIR}/cubebTargets.cmake")
check_required_components(cubeb)

View File

@ -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)

View File

@ -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
}

View File

@ -6,7 +6,6 @@
<ClInclude Include="include\cubeb\cubeb_export.h" /> <ClInclude Include="include\cubeb\cubeb_export.h" />
<ClInclude Include="src\cubeb-internal.h" /> <ClInclude Include="src\cubeb-internal.h" />
<ClInclude Include="src\cubeb-speex-resampler.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_assert.h" />
<ClInclude Include="src\cubeb_log.h" /> <ClInclude Include="src\cubeb_log.h" />
<ClInclude Include="src\cubeb_mixer.h" /> <ClInclude Include="src\cubeb_mixer.h" />

View File

@ -5,7 +5,6 @@
<ClInclude Include="include\cubeb\cubeb_export.h" /> <ClInclude Include="include\cubeb\cubeb_export.h" />
<ClInclude Include="src\cubeb-internal.h" /> <ClInclude Include="src\cubeb-internal.h" />
<ClInclude Include="src\cubeb-speex-resampler.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_assert.h" />
<ClInclude Include="src\cubeb_log.h" /> <ClInclude Include="src\cubeb_log.h" />
<ClInclude Include="src\cubeb_mixer.h" /> <ClInclude Include="src\cubeb_mixer.h" />

View File

@ -163,6 +163,7 @@ typedef enum {
implications. */ implications. */
} cubeb_log_level; } cubeb_log_level;
/// A single channel position, to be used in a bitmask.
typedef enum { typedef enum {
CHANNEL_UNKNOWN = 0, CHANNEL_UNKNOWN = 0,
CHANNEL_FRONT_LEFT = 1 << 0, CHANNEL_FRONT_LEFT = 1 << 0,
@ -185,6 +186,9 @@ typedef enum {
CHANNEL_TOP_BACK_RIGHT = 1 << 17 CHANNEL_TOP_BACK_RIGHT = 1 << 17
} cubeb_channel; } 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; typedef uint32_t cubeb_channel_layout;
// Some common layout definitions. // Some common layout definitions.
enum { 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. * 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); typedef void (*cubeb_device_changed_callback)(void * user_ptr);
/** /**

View File

@ -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;

View File

@ -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_

View File

@ -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_

View File

@ -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_ */

View File

@ -3,6 +3,10 @@
typedef struct cubeb_jni cubeb_jni; typedef struct cubeb_jni cubeb_jni;
#ifdef __cplusplus
extern "C" {
#endif
cubeb_jni * cubeb_jni *
cubeb_jni_init(); cubeb_jni_init();
int int
@ -10,4 +14,8 @@ cubeb_get_output_latency_from_jni(cubeb_jni * cubeb_jni_ptr);
void void
cubeb_jni_destroy(cubeb_jni * cubeb_jni_ptr); cubeb_jni_destroy(cubeb_jni * cubeb_jni_ptr);
#ifdef __cplusplus
};
#endif
#endif // _CUBEB_JNI_H_ #endif // _CUBEB_JNI_H_

View File

@ -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

View File

@ -31,10 +31,6 @@ struct cubeb_stream {
int int
pulse_init(cubeb ** context, char const * context_name); pulse_init(cubeb ** context, char const * context_name);
#endif #endif
#if defined(USE_PULSE_RUST)
int
pulse_rust_init(cubeb ** contet, char const * context_name);
#endif
#if defined(USE_JACK) #if defined(USE_JACK)
int int
jack_init(cubeb ** context, char const * context_name); jack_init(cubeb ** context, char const * context_name);
@ -47,10 +43,6 @@ alsa_init(cubeb ** context, char const * context_name);
int int
audiounit_init(cubeb ** context, char const * context_name); audiounit_init(cubeb ** context, char const * context_name);
#endif #endif
#if defined(USE_AUDIOUNIT_RUST)
int
audiounit_rust_init(cubeb ** contet, char const * context_name);
#endif
#if defined(USE_WINMM) #if defined(USE_WINMM)
int int
winmm_init(cubeb ** context, char const * context_name); winmm_init(cubeb ** context, char const * context_name);
@ -63,30 +55,10 @@ wasapi_init(cubeb ** context, char const * context_name);
int int
sndio_init(cubeb ** context, char const * context_name); sndio_init(cubeb ** context, char const * context_name);
#endif #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) #if defined(USE_OSS)
int int
oss_init(cubeb ** context, char const * context_name); oss_init(cubeb ** context, char const * context_name);
#endif #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 static int
validate_stream_params(cubeb_stream_params * input_stream_params, 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 (!strcmp(backend_name, "pulse")) {
#if defined(USE_PULSE) #if defined(USE_PULSE)
init_oneshot = pulse_init; init_oneshot = pulse_init;
#endif
} else if (!strcmp(backend_name, "pulse-rust")) {
#if defined(USE_PULSE_RUST)
init_oneshot = pulse_rust_init;
#endif #endif
} else if (!strcmp(backend_name, "jack")) { } else if (!strcmp(backend_name, "jack")) {
#if defined(USE_JACK) #if defined(USE_JACK)
@ -167,10 +135,6 @@ cubeb_init(cubeb ** context, char const * context_name,
} else if (!strcmp(backend_name, "audiounit")) { } else if (!strcmp(backend_name, "audiounit")) {
#if defined(USE_AUDIOUNIT) #if defined(USE_AUDIOUNIT)
init_oneshot = audiounit_init; init_oneshot = audiounit_init;
#endif
} else if (!strcmp(backend_name, "audiounit-rust")) {
#if defined(USE_AUDIOUNIT_RUST)
init_oneshot = audiounit_rust_init;
#endif #endif
} else if (!strcmp(backend_name, "wasapi")) { } else if (!strcmp(backend_name, "wasapi")) {
#if defined(USE_WASAPI) #if defined(USE_WASAPI)
@ -183,30 +147,10 @@ cubeb_init(cubeb ** context, char const * context_name,
} else if (!strcmp(backend_name, "sndio")) { } else if (!strcmp(backend_name, "sndio")) {
#if defined(USE_SNDIO) #if defined(USE_SNDIO)
init_oneshot = sndio_init; 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 #endif
} else if (!strcmp(backend_name, "oss")) { } else if (!strcmp(backend_name, "oss")) {
#if defined(USE_OSS) #if defined(USE_OSS)
init_oneshot = oss_init; init_oneshot = oss_init;
#endif
} else if (!strcmp(backend_name, "aaudio")) {
#if defined(USE_AAUDIO)
init_oneshot = aaudio_init;
#endif
} 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 #endif
} else { } else {
/* Already set */ /* Already set */
@ -219,9 +163,6 @@ cubeb_init(cubeb ** context, char const * context_name,
* to override all other choices * to override all other choices
*/ */
init_oneshot, init_oneshot,
#if defined(USE_PULSE_RUST)
pulse_rust_init,
#endif
#if defined(USE_PULSE) #if defined(USE_PULSE)
pulse_init, pulse_init,
#endif #endif
@ -237,9 +178,6 @@ cubeb_init(cubeb ** context, char const * context_name,
#if defined(USE_OSS) #if defined(USE_OSS)
oss_init, oss_init,
#endif #endif
#if defined(USE_AUDIOUNIT_RUST)
audiounit_rust_init,
#endif
#if defined(USE_AUDIOUNIT) #if defined(USE_AUDIOUNIT)
audiounit_init, audiounit_init,
#endif #endif
@ -251,20 +189,6 @@ cubeb_init(cubeb ** context, char const * context_name,
#endif #endif
#if defined(USE_SUN) #if defined(USE_SUN)
sun_init, 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 #endif
}; };
int i; int i;
@ -297,9 +221,6 @@ cubeb_get_backend_names()
#if defined(USE_PULSE) #if defined(USE_PULSE)
"pulse", "pulse",
#endif #endif
#if defined(USE_PULSE_RUST)
"pulse-rust",
#endif
#if defined(USE_JACK) #if defined(USE_JACK)
"jack", "jack",
#endif #endif
@ -309,9 +230,6 @@ cubeb_get_backend_names()
#if defined(USE_AUDIOUNIT) #if defined(USE_AUDIOUNIT)
"audiounit", "audiounit",
#endif #endif
#if defined(USE_AUDIOUNIT_RUST)
"audiounit-rust",
#endif
#if defined(USE_WASAPI) #if defined(USE_WASAPI)
"wasapi", "wasapi",
#endif #endif
@ -324,20 +242,8 @@ cubeb_get_backend_names()
#if defined(USE_SUN) #if defined(USE_SUN)
"sun", "sun",
#endif #endif
#if defined(USE_OPENSL)
"opensl",
#endif
#if defined(USE_OSS) #if defined(USE_OSS)
"oss", "oss",
#endif
#if defined(USE_AAUDIO)
"aaudio",
#endif
#if defined(USE_AUDIOTRACK)
"audiotrack",
#endif
#if defined(USE_KAI)
"kai",
#endif #endif
NULL, NULL,
}; };
@ -406,6 +312,8 @@ cubeb_destroy(cubeb * context)
} }
context->ops->destroy(context); context->ops->destroy(context);
cubeb_set_log_callback(CUBEB_LOG_DISABLED, NULL);
} }
int int
@ -687,14 +595,14 @@ cubeb_enumerate_devices(cubeb * context, cubeb_device_type devtype,
int rv; int rv;
if ((devtype & (CUBEB_DEVICE_TYPE_INPUT | CUBEB_DEVICE_TYPE_OUTPUT)) == 0) if ((devtype & (CUBEB_DEVICE_TYPE_INPUT | CUBEB_DEVICE_TYPE_OUTPUT)) == 0)
return CUBEB_ERROR_INVALID_PARAMETER; return CUBEB_ERROR_INVALID_PARAMETER;
if (collection == NULL) if (context == NULL || collection == NULL)
return CUBEB_ERROR_INVALID_PARAMETER; return CUBEB_ERROR_INVALID_PARAMETER;
if (!context->ops->enumerate_devices) if (!context->ops->enumerate_devices)
return CUBEB_ERROR_NOT_SUPPORTED; return CUBEB_ERROR_NOT_SUPPORTED;
rv = context->ops->enumerate_devices(context, devtype, collection); 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++) { for (size_t i = 0; i < collection->count; i++) {
log_device(&collection->device[i]); log_device(&collection->device[i]);
} }
@ -756,21 +664,11 @@ cubeb_set_log_callback(cubeb_log_level log_level,
return CUBEB_ERROR_INVALID_PARAMETER; return CUBEB_ERROR_INVALID_PARAMETER;
} }
if (g_cubeb_log_callback && log_callback) { if (cubeb_log_get_callback() && log_callback) {
return CUBEB_ERROR_NOT_SUPPORTED; return CUBEB_ERROR_NOT_SUPPORTED;
} }
g_cubeb_log_callback = log_callback; cubeb_log_set(log_level, log_callback);
g_cubeb_log_level = log_level;
// Logging a message here allows to initialize the asynchronous logger from a
// thread that is not the audio rendering thread, and especially to not
// initialize it the first time we find a verbose log, which is often in the
// audio rendering callback, that runs from the audio rendering thread, and
// that is high priority, and that we don't want to block.
if (log_level >= CUBEB_LOG_VERBOSE) {
ALOGV("Starting cubeb log");
}
return CUBEB_OK; return CUBEB_OK;
} }

File diff suppressed because it is too large Load Diff

View File

@ -7,9 +7,13 @@
#undef NDEBUG #undef NDEBUG
#define _DEFAULT_SOURCE #define _DEFAULT_SOURCE
#define _BSD_SOURCE #define _BSD_SOURCE
#if defined(__NetBSD__)
#define _NETBSD_SOURCE /* timersub() */
#endif
#define _XOPEN_SOURCE 500 #define _XOPEN_SOURCE 500
#include "cubeb-internal.h" #include "cubeb-internal.h"
#include "cubeb/cubeb.h" #include "cubeb/cubeb.h"
#include "cubeb_tracing.h"
#include <alsa/asoundlib.h> #include <alsa/asoundlib.h>
#include <assert.h> #include <assert.h>
#include <dlfcn.h> #include <dlfcn.h>
@ -579,10 +583,14 @@ alsa_run_thread(void * context)
cubeb * ctx = context; cubeb * ctx = context;
int r; int r;
CUBEB_REGISTER_THREAD("cubeb rendering thread");
do { do {
r = alsa_run(ctx); r = alsa_run(ctx);
} while (r >= 0); } while (r >= 0);
CUBEB_UNREGISTER_THREAD();
return NULL; return NULL;
} }
@ -957,11 +965,11 @@ alsa_destroy(cubeb * ctx)
WRAP(snd_config_delete)(ctx->local_config); WRAP(snd_config_delete)(ctx->local_config);
pthread_mutex_unlock(&cubeb_alsa_mutex); pthread_mutex_unlock(&cubeb_alsa_mutex);
} }
#ifndef DISABLE_LIBASOUND_DLOPEN
if (ctx->libasound) { if (ctx->libasound) {
dlclose(ctx->libasound); dlclose(ctx->libasound);
} }
#endif
free(ctx); free(ctx);
} }

View File

@ -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

View File

@ -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

View File

@ -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, &params, (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};

View File

@ -8,7 +8,7 @@
*/ */
#define _DEFAULT_SOURCE #define _DEFAULT_SOURCE
#define _BSD_SOURCE #define _BSD_SOURCE
#ifndef __FreeBSD__ #if !defined(__FreeBSD__) && !defined(__NetBSD__)
#define _POSIX_SOURCE #define _POSIX_SOURCE
#endif #endif
#include "cubeb-internal.h" #include "cubeb-internal.h"
@ -788,10 +788,10 @@ cbjack_destroy(cubeb * context)
if (context->jack_client != NULL) if (context->jack_client != NULL)
WRAP(jack_client_close)(context->jack_client); WRAP(jack_client_close)(context->jack_client);
#ifndef DISABLE_LIBJACK_DLOPEN
if (context->libjack) if (context->libjack)
dlclose(context->libjack); dlclose(context->libjack);
#endif
free(context); free(context);
} }

View File

@ -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};

View File

@ -16,8 +16,8 @@
#include <time.h> #include <time.h>
#endif #endif
cubeb_log_level g_cubeb_log_level; static std::atomic<cubeb_log_level> g_cubeb_log_level;
cubeb_log_callback g_cubeb_log_callback; static std::atomic<cubeb_log_callback> g_cubeb_log_callback;
/** The maximum size of a log message, after having been formatted. */ /** The maximum size of a log message, after having been formatted. */
const size_t CUBEB_LOG_MESSAGE_MAX_SIZE = 256; const size_t CUBEB_LOG_MESSAGE_MAX_SIZE = 256;
@ -25,111 +25,56 @@ const size_t CUBEB_LOG_MESSAGE_MAX_SIZE = 256;
* messages. */ * messages. */
const size_t CUBEB_LOG_MESSAGE_QUEUE_DEPTH = 40; const size_t CUBEB_LOG_MESSAGE_QUEUE_DEPTH = 40;
/** Number of milliseconds to wait before dequeuing log messages. */ /** Number of milliseconds to wait before dequeuing log messages. */
#define CUBEB_LOG_BATCH_PRINT_INTERVAL_MS 10 const size_t 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;
};
void 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_list args;
va_start(args, fmt); va_start(args, fmt);
char msg[CUBEB_LOG_MESSAGE_MAX_SIZE]; char msg[CUBEB_LOG_MESSAGE_MAX_SIZE];
vsnprintf(msg, CUBEB_LOG_MESSAGE_MAX_SIZE, fmt, args); vsnprintf(msg, CUBEB_LOG_MESSAGE_MAX_SIZE, fmt, args);
cubeb_async_logger::get().push(msg);
va_end(args); va_end(args);
g_cubeb_log_callback.load()("%s:%d:%s", file, line, msg);
} }
void void
cubeb_async_log_reset_threads(void) cubeb_log_internal_no_format(const char * msg)
{ {
if (!g_cubeb_log_callback) { g_cubeb_log_callback.load()(msg);
return; }
}
cubeb_async_logger::get().reset_producer_thread(); 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;
} }

View File

@ -30,12 +30,16 @@ extern "C" {
(strrchr(__FILE__, '/') ? strrchr(__FILE__, '/') + 1 : __FILE__) (strrchr(__FILE__, '/') ? strrchr(__FILE__, '/') + 1 : __FILE__)
#endif #endif
extern cubeb_log_level g_cubeb_log_level;
extern cubeb_log_callback g_cubeb_log_callback PRINTF_FORMAT(1, 2);
void 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 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 #ifdef __cplusplus
} }
@ -44,31 +48,16 @@ cubeb_async_log_reset_threads(void);
#define LOGV(msg, ...) LOG_INTERNAL(CUBEB_LOG_VERBOSE, msg, ##__VA_ARGS__) #define LOGV(msg, ...) LOG_INTERNAL(CUBEB_LOG_VERBOSE, msg, ##__VA_ARGS__)
#define LOG(msg, ...) LOG_INTERNAL(CUBEB_LOG_NORMAL, msg, ##__VA_ARGS__) #define LOG(msg, ...) LOG_INTERNAL(CUBEB_LOG_NORMAL, msg, ##__VA_ARGS__)
#define LOG_INTERNAL_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, ...) \ #define LOG_INTERNAL(level, fmt, ...) \
do { \ do { \
if (g_cubeb_log_callback && level <= g_cubeb_log_level) { \ if (cubeb_log_get_level() >= level && cubeb_log_get_callback()) { \
g_cubeb_log_callback("%s:%d: " fmt "\n", __FILENAME__, __LINE__, \ cubeb_log_internal(__FILENAME__, __LINE__, fmt, ##__VA_ARGS__); \
##__VA_ARGS__); \
} \
} while (0)
#define ALOG_INTERNAL(level, fmt, ...) \
do { \
if (level <= g_cubeb_log_level) { \
cubeb_async_log(fmt, ##__VA_ARGS__); \
} \ } \
} while (0) } while (0)
/* Asynchronous logging macros to log in real-time callbacks. */ /* Asynchronous logging macros to log in real-time callbacks. */
/* Should not be used on android due to the use of global/static variables. */ /* 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 ALOGV(msg, ...) LOG_INTERNAL(CUBEB_LOG_VERBOSE, msg, ##__VA_ARGS__)
#define ALOG(msg, ...) ALOG_INTERNAL(CUBEB_LOG_NORMAL, msg, ##__VA_ARGS__) #define ALOG(msg, ...) LOG_INTERNAL(CUBEB_LOG_NORMAL, msg, ##__VA_ARGS__)
#endif // CUBEB_LOG #endif // CUBEB_LOG

View File

@ -183,7 +183,7 @@ MixerContext::auto_matrix()
{ {
double matrix[NUM_NAMED_CHANNELS][NUM_NAMED_CHANNELS] = {{0}}; double matrix[NUM_NAMED_CHANNELS][NUM_NAMED_CHANNELS] = {{0}};
double maxcoef = 0; double maxcoef = 0;
float maxval; double maxval;
cubeb_channel_layout in_ch_layout = clean_layout(_in_ch_layout); cubeb_channel_layout in_ch_layout = clean_layout(_in_ch_layout);
cubeb_channel_layout out_ch_layout = clean_layout(_out_ch_layout); cubeb_channel_layout out_ch_layout = clean_layout(_out_ch_layout);

File diff suppressed because it is too large Load Diff

View File

@ -14,6 +14,7 @@
#include "cubeb/cubeb.h" #include "cubeb/cubeb.h"
#include "cubeb_mixer.h" #include "cubeb_mixer.h"
#include "cubeb_strings.h" #include "cubeb_strings.h"
#include "cubeb_tracing.h"
#include <assert.h> #include <assert.h>
#include <ctype.h> #include <ctype.h>
#include <errno.h> #include <errno.h>
@ -975,6 +976,8 @@ oss_io_routine(void * arg)
cubeb_state new_state; cubeb_state new_state;
int stopped; int stopped;
CUBEB_REGISTER_THREAD("cubeb rendering thread");
do { do {
pthread_mutex_lock(&s->mtx); pthread_mutex_lock(&s->mtx);
if (s->destroying) { if (s->destroying) {
@ -1005,6 +1008,9 @@ oss_io_routine(void * arg)
pthread_mutex_lock(&s->mtx); pthread_mutex_lock(&s->mtx);
s->thread_created = false; s->thread_created = false;
pthread_mutex_unlock(&s->mtx); pthread_mutex_unlock(&s->mtx);
CUBEB_UNREGISTER_THREAD();
return NULL; return NULL;
} }

View File

@ -804,10 +804,11 @@ pulse_destroy(cubeb * ctx)
if (ctx->device_ids) { if (ctx->device_ids) {
cubeb_strings_destroy(ctx->device_ids); cubeb_strings_destroy(ctx->device_ids);
} }
#ifndef DISABLE_LIBPULSE_DLOPEN
if (ctx->libpulse) { if (ctx->libpulse) {
dlclose(ctx->libpulse); dlclose(ctx->libpulse);
} }
#endif
free(ctx->default_sink_info); free(ctx->default_sink_info);
free(ctx); free(ctx);
} }
@ -1024,7 +1025,7 @@ pulse_stream_init(cubeb * context, cubeb_stream ** stream,
return CUBEB_ERROR; return CUBEB_ERROR;
} }
if (g_cubeb_log_level) { if (cubeb_log_get_level()) {
if (output_stream_params) { if (output_stream_params) {
const pa_buffer_attr * output_att; const pa_buffer_attr * output_att;
output_att = WRAP(pa_stream_get_buffer_attr)(stm->output_stream); output_att = WRAP(pa_stream_get_buffer_attr)(stm->output_stream);
@ -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_SOURCE:
case PA_SUBSCRIPTION_EVENT_SINK: case PA_SUBSCRIPTION_EVENT_SINK:
if (g_cubeb_log_level) { if (cubeb_log_get_level()) {
if ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == if ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) ==
PA_SUBSCRIPTION_EVENT_SOURCE && PA_SUBSCRIPTION_EVENT_SOURCE &&
(t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == (t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) ==

View File

@ -9,6 +9,7 @@
#define CUBEB_RING_ARRAY_H #define CUBEB_RING_ARRAY_H
#include "cubeb_utils.h" #include "cubeb_utils.h"
#include <CoreAudio/CoreAudioTypes.h>
/** Ring array of pointers is used to hold buffers. In case that /** Ring array of pointers is used to hold buffers. In case that
asynchronous producer/consumer callbacks do not arrive in a 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. /** Initialize the ring array.
@param ra The ring_array pointer of allocated structure. @param ra The ring_array pointer of allocated structure.
@retval 0 on success. */ @retval 0 on success. */
int static int
ring_array_init(ring_array * ra, uint32_t capacity, uint32_t bytesPerFrame, ring_array_init(ring_array * ra, uint32_t capacity, uint32_t bytesPerFrame,
uint32_t channelsPerFrame, uint32_t framesPerBuffer) 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. /** Destroy the ring array.
@param ra The ring_array pointer.*/ @param ra The ring_array pointer.*/
void static void
ring_array_destroy(ring_array * ra) ring_array_destroy(ring_array * ra)
{ {
assert(ra); assert(ra);
@ -97,7 +98,7 @@ ring_array_destroy(ring_array * ra)
@param ra The ring_array pointer. @param ra The ring_array pointer.
@retval Pointer of the allocated space to be stored with fresh data or NULL @retval Pointer of the allocated space to be stored with fresh data or NULL
if full. */ if full. */
AudioBuffer * static AudioBuffer *
ring_array_get_free_buffer(ring_array * ra) ring_array_get_free_buffer(ring_array * ra)
{ {
assert(ra && ra->buffer_array); assert(ra && ra->buffer_array);
@ -118,7 +119,7 @@ ring_array_get_free_buffer(ring_array * ra)
/** Get the next available buffer with data. /** Get the next available buffer with data.
@param ra The ring_array pointer. @param ra The ring_array pointer.
@retval Pointer of the next in order data buffer or NULL if empty. */ @retval Pointer of the next in order data buffer or NULL if empty. */
AudioBuffer * static AudioBuffer *
ring_array_get_data_buffer(ring_array * ra) ring_array_get_data_buffer(ring_array * ra)
{ {
assert(ra && ra->buffer_array); assert(ra && ra->buffer_array);
@ -138,18 +139,4 @@ ring_array_get_data_buffer(ring_array * ra)
return ret; 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 #endif // CUBEB_RING_ARRAY_H

View File

@ -6,6 +6,7 @@
*/ */
#include "cubeb-internal.h" #include "cubeb-internal.h"
#include "cubeb/cubeb.h" #include "cubeb/cubeb.h"
#include "cubeb_tracing.h"
#include <assert.h> #include <assert.h>
#include <dlfcn.h> #include <dlfcn.h>
#include <inttypes.h> #include <inttypes.h>
@ -67,7 +68,7 @@ struct cubeb_stream {
struct sio_hdl * hdl; /* link us to sndio */ struct sio_hdl * hdl; /* link us to sndio */
int mode; /* bitmap of SIO_{PLAY,REC} */ int mode; /* bitmap of SIO_{PLAY,REC} */
int active; /* cubec_start() called */ int active; /* cubec_start() called */
int conv; /* need float->s16 conversion */ int conv; /* need float->s24 conversion */
unsigned char * rbuf; /* rec data consumed from here */ unsigned char * rbuf; /* rec data consumed from here */
unsigned char * pbuf; /* play data is prepared here */ unsigned char * pbuf; /* play data is prepared here */
unsigned int nfr; /* number of frames in ibuf and obuf */ unsigned int nfr; /* number of frames in ibuf and obuf */
@ -98,33 +99,33 @@ s16_setvol(void * ptr, long nsamp, float volume)
} }
static void 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 * src = ptr;
float mult = volume * 32768; float mult = volume * 8388608;
int s; int s;
while (nsamp-- > 0) { while (nsamp-- > 0) {
s = lrintf(*(src++) * mult); s = lrintf(*(src++) * mult);
if (s < -32768) if (s < -8388608)
s = -32768; s = -8388608;
else if (s > 32767) else if (s > 8388607)
s = 32767; s = 8388607;
*(dst++) = s; *(dst++) = s;
} }
} }
static void 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; float * dst = ptr;
src += nsamp; src += nsamp;
dst += nsamp; dst += nsamp;
while (nsamp-- > 0) while (nsamp-- > 0)
*(--dst) = (1. / 32768) * *(--src); *(--dst) = (1. / 8388608) * *(--src);
} }
static const char * static const char *
@ -161,10 +162,14 @@ sndio_mainloop(void * arg)
size_t pstart = 0, pend = 0, rstart = 0, rend = 0; size_t pstart = 0, pend = 0, rstart = 0, rend = 0;
long nfr; long nfr;
CUBEB_REGISTER_THREAD("cubeb rendering thread");
nfds = WRAP(sio_nfds)(s->hdl); nfds = WRAP(sio_nfds)(s->hdl);
pfds = calloc(nfds, sizeof(struct pollfd)); pfds = calloc(nfds, sizeof(struct pollfd));
if (pfds == NULL) if (pfds == NULL) {
CUBEB_UNREGISTER_THREAD();
return NULL; return NULL;
}
DPR("sndio_mainloop()\n"); DPR("sndio_mainloop()\n");
s->state_cb(s, s->arg, CUBEB_STATE_STARTED); s->state_cb(s, s->arg, CUBEB_STATE_STARTED);
@ -172,6 +177,7 @@ sndio_mainloop(void * arg)
if (!WRAP(sio_start)(s->hdl)) { if (!WRAP(sio_start)(s->hdl)) {
pthread_mutex_unlock(&s->mtx); pthread_mutex_unlock(&s->mtx);
free(pfds); free(pfds);
CUBEB_UNREGISTER_THREAD();
return NULL; return NULL;
} }
DPR("sndio_mainloop(), started\n"); DPR("sndio_mainloop(), started\n");
@ -207,7 +213,7 @@ sndio_mainloop(void * arg)
} }
if ((s->mode & SIO_REC) && s->conv) 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 */ /* invoke call-back, it returns less that s->nfr if done */
pthread_mutex_unlock(&s->mtx); pthread_mutex_unlock(&s->mtx);
@ -238,7 +244,7 @@ sndio_mainloop(void * arg)
if (s->mode & SIO_PLAY) { if (s->mode & SIO_PLAY) {
if (s->conv) if (s->conv)
float_to_s16(s->pbuf, nfr * s->pchan, s->volume); float_to_s24(s->pbuf, nfr * s->pchan, s->volume);
else else
s16_setvol(s->pbuf, nfr * s->pchan, s->volume); s16_setvol(s->pbuf, nfr * s->pchan, s->volume);
} }
@ -300,6 +306,7 @@ sndio_mainloop(void * arg)
pthread_mutex_unlock(&s->mtx); pthread_mutex_unlock(&s->mtx);
s->state_cb(s, s->arg, state); s->state_cb(s, s->arg, state);
free(pfds); free(pfds);
CUBEB_UNREGISTER_THREAD();
return NULL; return NULL;
} }
@ -362,8 +369,10 @@ static void
sndio_destroy(cubeb * context) sndio_destroy(cubeb * context)
{ {
DPR("sndio_destroy()\n"); DPR("sndio_destroy()\n");
#ifndef DISABLE_LIBSNDIO_DLOPEN
if (context->libsndio) if (context->libsndio)
dlclose(context->libsndio); dlclose(context->libsndio);
#endif
free(context); free(context);
} }
@ -420,21 +429,25 @@ sndio_stream_init(cubeb * context, cubeb_stream ** stream,
} }
WRAP(sio_initpar)(&wpar); WRAP(sio_initpar)(&wpar);
wpar.sig = 1; wpar.sig = 1;
wpar.bits = 16;
switch (format) { switch (format) {
case CUBEB_SAMPLE_S16LE: case CUBEB_SAMPLE_S16LE:
wpar.le = 1; wpar.le = 1;
wpar.bits = 16;
break; break;
case CUBEB_SAMPLE_S16BE: case CUBEB_SAMPLE_S16BE:
wpar.le = 0; wpar.le = 0;
wpar.bits = 16;
break; break;
case CUBEB_SAMPLE_FLOAT32NE: case CUBEB_SAMPLE_FLOAT32NE:
wpar.le = SIO_LE_NATIVE; wpar.le = SIO_LE_NATIVE;
wpar.bits = 24;
wpar.msb = 0;
break; break;
default: default:
DPR("sndio_stream_init() unsupported format\n"); DPR("sndio_stream_init() unsupported format\n");
goto err; goto err;
} }
wpar.bps = SIO_BPS(wpar.bits);
wpar.rate = rate; wpar.rate = rate;
if (s->mode & SIO_REC) if (s->mode & SIO_REC)
wpar.rchan = input_stream_params->channels; wpar.rchan = input_stream_params->channels;
@ -446,6 +459,8 @@ sndio_stream_init(cubeb * context, cubeb_stream ** stream,
goto err; goto err;
} }
if (rpar.bits != wpar.bits || rpar.le != wpar.le || rpar.sig != wpar.sig || 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 || rpar.rate != wpar.rate ||
((s->mode & SIO_REC) && rpar.rchan != wpar.rchan) || ((s->mode & SIO_REC) && rpar.rchan != wpar.rchan) ||
((s->mode & SIO_PLAY) && rpar.pchan != wpar.pchan)) { ((s->mode & SIO_PLAY) && rpar.pchan != wpar.pchan)) {

View File

@ -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};

View 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

View File

@ -24,12 +24,17 @@
#include <vector> #include <vector>
#include <windef.h> #include <windef.h>
#include <windows.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-internal.h"
#include "cubeb/cubeb.h" #include "cubeb/cubeb.h"
#include "cubeb_mixer.h" #include "cubeb_mixer.h"
#include "cubeb_resampler.h" #include "cubeb_resampler.h"
#include "cubeb_strings.h" #include "cubeb_strings.h"
#include "cubeb_tracing.h"
#include "cubeb_utils.h" #include "cubeb_utils.h"
// Windows 10 exposes the IAudioClient3 interface to create low-latency streams. // 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 int64_t LATENCY_NOT_AVAILABLE_YET = -1;
const DWORD DEVICE_CHANGE_DEBOUNCE_MS = 250;
struct com_heap_ptr_deleter { struct com_heap_ptr_deleter {
void operator()(void * ptr) const noexcept { CoTaskMemFree(ptr); } void operator()(void * ptr) const noexcept { CoTaskMemFree(ptr); }
}; };
@ -103,7 +110,9 @@ struct com_heap_ptr_deleter {
template <typename T> template <typename T>
using com_heap_ptr = std::unique_ptr<T, com_heap_ptr_deleter>; 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; return N;
} }
@ -181,6 +190,20 @@ private:
T * ptr = nullptr; 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; extern cubeb_ops const wasapi_ops;
static com_heap_ptr<wchar_t> static com_heap_ptr<wchar_t>
@ -223,6 +246,11 @@ private:
com_heap_ptr<wchar_t> capture_comms_id; com_heap_ptr<wchar_t> capture_comms_id;
}; };
struct AutoRegisterThread {
AutoRegisterThread(const char * name) { CUBEB_REGISTER_THREAD(name); }
~AutoRegisterThread() { CUBEB_UNREGISTER_THREAD(); }
};
int int
wasapi_stream_stop(cubeb_stream * stm); wasapi_stream_stop(cubeb_stream * stm);
int int
@ -365,8 +393,8 @@ struct cubeb_stream {
com_ptr<IAudioClient> input_client; com_ptr<IAudioClient> input_client;
/* Interface to use the event driven capture interface */ /* Interface to use the event driven capture interface */
com_ptr<IAudioCaptureClient> capture_client; com_ptr<IAudioCaptureClient> capture_client;
/* This event is set by the stream_stop and stream_destroy /* This event is set by the stream_destroy function, so the render loop can
function, so the render loop can exit properly. */ exit properly. */
HANDLE shutdown_event = 0; HANDLE shutdown_event = 0;
/* Set by OnDefaultDeviceChanged when a stream reconfiguration is required. /* Set by OnDefaultDeviceChanged when a stream reconfiguration is required.
The reconfiguration is handled by the render loop thread. */ The reconfiguration is handled by the render loop thread. */
@ -410,17 +438,23 @@ struct cubeb_stream {
float volume = 1.0; float volume = 1.0;
/* True if the stream is draining. */ /* True if the stream is draining. */
bool draining = false; 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 /* This needs an active audio input stream to be known, and is updated in the
* first audio input callback. */ * first audio input callback. */
std::atomic<int64_t> input_latency_hns{LATENCY_NOT_AVAILABLE_YET}; std::atomic<int64_t> input_latency_hns{LATENCY_NOT_AVAILABLE_YET};
/* Those attributes count the number of frames requested (resp. received) by /* 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. */ 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_input_frames = 0;
size_t total_output_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 { class monitor_device_notifications {
@ -463,6 +497,7 @@ public:
private: private:
static unsigned int __stdcall thread_proc(LPVOID args) static unsigned int __stdcall thread_proc(LPVOID args)
{ {
AutoRegisterThread raii("WASAPI device notification thread");
XASSERT(args); XASSERT(args);
auto mdn = static_cast<monitor_device_notifications *>(args); auto mdn = static_cast<monitor_device_notifications *>(args);
mdn->notification_thread_loop(); mdn->notification_thread_loop();
@ -689,7 +724,8 @@ public:
} }
wasapi_endpoint_notification_client(HANDLE event, ERole role) 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, HRESULT STDMETHODCALLTYPE OnDefaultDeviceChanged(EDataFlow flow, ERole role,
LPCWSTR device_id) 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. */ /* we only support a single stream type for now. */
if (flow != eRender && role != this->role) { if (flow != eRender || role != this->role) {
return S_OK; return S_OK;
} }
BOOL ok = SetEvent(reconfigure_event); DWORD last_change_ms = timeGetTime() - last_device_change;
if (!ok) { bool same_device = default_device_id && device_id &&
LOG("endpoint: SetEvent on reconfigure_event failed: %lx", wcscmp(default_device_id.get(), device_id) == 0;
GetLastError()); 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; return S_OK;
@ -747,6 +798,8 @@ private:
LONG ref_count; LONG ref_count;
HANDLE reconfigure_event; HANDLE reconfigure_event;
ERole role; ERole role;
std::unique_ptr<const wchar_t[]> default_device_id;
DWORD last_device_change;
}; };
namespace { namespace {
@ -756,9 +809,6 @@ wasapi_data_callback(cubeb_stream * stm, void * user_ptr,
void const * input_buffer, void * output_buffer, void const * input_buffer, void * output_buffer,
long nframes) long nframes)
{ {
if (stm->emergency_bailout) {
return CUBEB_ERROR;
}
return stm->data_callback(stm, user_ptr, input_buffer, output_buffer, return stm->data_callback(stm, user_ptr, input_buffer, output_buffer,
nframes); nframes);
} }
@ -766,9 +816,6 @@ wasapi_data_callback(cubeb_stream * stm, void * user_ptr,
void void
wasapi_state_callback(cubeb_stream * stm, void * user_ptr, cubeb_state state) 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); 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); long got = refill(stm, nullptr, 0, output_buffer, output_frames);
ALOGV("Output callback: output frames requested: %Iu, got %ld", output_frames, if (got != output_frames) {
got); ALOGV("Output callback: output frames requested: %Iu, got %ld", output_frames,
got);
}
if (got < 0) { if (got < 0) {
return false; return false;
} }
@ -1322,29 +1371,12 @@ refill_callback_output(cubeb_stream * stm)
void void
wasapi_stream_destroy(cubeb_stream * stm); 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) static unsigned int __stdcall wasapi_stream_render_loop(LPVOID stream)
{ {
AutoRegisterThread raii("cubeb rendering thread");
cubeb_stream * stm = static_cast<cubeb_stream *>(stream); cubeb_stream * stm = static_cast<cubeb_stream *>(stream);
bool is_playing = true; auto_stream_ref stream_ref(stm);
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;
struct auto_com { struct auto_com {
auto_com() auto_com()
{ {
@ -1354,6 +1386,21 @@ static unsigned int __stdcall wasapi_stream_render_loop(LPVOID stream)
~auto_com() { CoUninitialize(); } ~auto_com() { CoUninitialize(); }
} com; } 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 /* We could consider using "Pro Audio" here for WebAudio and
maybe WebRTC. */ maybe WebRTC. */
mmcss_handle = AvSetMmThreadCharacteristicsA("Audio", &mmcss_task_index); mmcss_handle = AvSetMmThreadCharacteristicsA("Audio", &mmcss_task_index);
@ -1363,20 +1410,9 @@ static unsigned int __stdcall wasapi_stream_render_loop(LPVOID stream)
GetLastError()); 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) { while (is_playing) {
handle_emergency_bailout(stm);
DWORD waitResult = WaitForMultipleObjects(ARRAY_LENGTH(wait_array), DWORD waitResult = WaitForMultipleObjects(ARRAY_LENGTH(wait_array),
wait_array, FALSE, 1000); wait_array, FALSE, INFINITE);
handle_emergency_bailout(stm);
if (waitResult != WAIT_TIMEOUT) {
timeout_count = 0;
}
switch (waitResult) { switch (waitResult) {
case WAIT_OBJECT_0: { /* shutdown */ case WAIT_OBJECT_0: { /* shutdown */
is_playing = false; is_playing = false;
@ -1388,36 +1424,40 @@ static unsigned int __stdcall wasapi_stream_render_loop(LPVOID stream)
continue; continue;
} }
case WAIT_OBJECT_0 + 1: { /* reconfigure */ 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); XASSERT(stm->output_client || stm->input_client);
LOG("Reconfiguring the stream"); LOG("Reconfiguring the stream");
/* Close the stream */ /* Close the stream */
bool was_running = false;
if (stm->output_client) { if (stm->output_client) {
stm->output_client->Stop(); was_running = stm->output_client->Stop() == S_OK;
LOG("Output stopped."); LOG("Output stopped.");
} }
if (stm->input_client) { if (stm->input_client) {
stm->input_client->Stop(); was_running = stm->input_client->Stop() == S_OK;
LOG("Input stopped."); LOG("Input stopped.");
} }
{ close_wasapi_stream(stm);
auto_lock lock(stm->stream_reset_lock); LOG("Stream closed.");
close_wasapi_stream(stm); /* Reopen a stream and start it immediately. This will automatically
LOG("Stream closed."); pick the new default device for this role. */
/* Reopen a stream and start it immediately. This will automatically int r = setup_wasapi_stream(stm);
pick the new default device for this role. */ if (r != CUBEB_OK) {
int r = setup_wasapi_stream(stm); LOG("Error setting up the stream during reconfigure.");
if (r != CUBEB_OK) { /* Don't destroy the stream here, since we expect the caller to do
LOG("Error setting up the stream during reconfigure."); so after the error has propagated via the state callback. */
/* Don't destroy the stream here, since we expect the caller to do is_playing = false;
so after the error has propagated via the state callback. */ hr = E_FAIL;
is_playing = false; continue;
hr = E_FAIL;
continue;
}
LOG("Stream setup successfuly.");
} }
LOG("Stream setup successfuly.");
XASSERT(stm->output_client || stm->input_client); XASSERT(stm->output_client || stm->input_client);
if (stm->output_client) { if (was_running && stm->output_client) {
hr = stm->output_client->Start(); hr = stm->output_client->Start();
if (FAILED(hr)) { if (FAILED(hr)) {
LOG("Error starting output after reconfigure, error: %lx", 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."); LOG("Output started after reconfigure.");
} }
if (stm->input_client) { if (was_running && stm->input_client) {
hr = stm->input_client->Start(); hr = stm->input_client->Start();
if (FAILED(hr)) { if (FAILED(hr)) {
LOG("Error starting input after reconfiguring, error: %lx", 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; 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: default:
LOG("case %lu not handled in render loop.", waitResult); LOG("case %lu not handled in render loop.", waitResult);
XASSERT(false); XASSERT(false);
@ -1482,8 +1514,6 @@ static unsigned int __stdcall wasapi_stream_render_loop(LPVOID stream)
AvRevertMmThreadCharacteristics(mmcss_handle); AvRevertMmThreadCharacteristics(mmcss_handle);
} }
handle_emergency_bailout(stm);
if (FAILED(hr)) { if (FAILED(hr)) {
wasapi_state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR); wasapi_state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR);
} }
@ -1739,24 +1769,16 @@ namespace {
enum ShutdownPhase { OnStop, OnDestroy }; enum ShutdownPhase { OnStop, OnDestroy };
bool 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 LOG("%p: Stop and join render thread: %p", stm, stm->thread);
// 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));
if (!stm->thread) { if (!stm->thread) {
return true; return true;
} }
XASSERT(!stm->emergency_bailout);
BOOL ok = SetEvent(stm->shutdown_event); BOOL ok = SetEvent(stm->shutdown_event);
if (!ok) { if (!ok) {
LOG("stop_and_join_render_thread: SetEvent failed: %lx", GetLastError()); LOG("stop_and_join_render_thread: SetEvent failed: %lx", GetLastError());
stm->emergency_bailout = bailout;
return false; 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: " LOG("stop_and_join_render_thread: WaitForSingleObject on thread failed: "
"%lx, %lx", "%lx, %lx",
r, GetLastError()); r, GetLastError());
stm->emergency_bailout = bailout;
return false; 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; return true;
} }
void void
wasapi_destroy(cubeb * context) wasapi_destroy(cubeb * context)
{ {
auto_lock lock(context->lock); {
XASSERT(!context->device_collection_enumerator && auto_lock lock(context->lock);
!context->collection_notification_client); XASSERT(!context->device_collection_enumerator &&
!context->collection_notification_client);
if (context->device_ids) { if (context->device_ids) {
cubeb_strings_destroy(context->device_ids); cubeb_strings_destroy(context->device_ids);
}
} }
delete context; delete context;
@ -2469,8 +2483,8 @@ setup_wasapi_stream(cubeb_stream * stm)
std::unique_ptr<const wchar_t[]> selected_output_device_id; std::unique_ptr<const wchar_t[]> selected_output_device_id;
if (stm->output_device_id) { if (stm->output_device_id) {
if (std::unique_ptr<wchar_t[]> tmp = if (std::unique_ptr<wchar_t[]> tmp =
move(copy_wide_string(stm->output_device_id.get()))) { copy_wide_string(stm->output_device_id.get())) {
selected_output_device_id = move(tmp); selected_output_device_id = std::move(tmp);
} else { } else {
LOG("Failed to copy output device identifier."); LOG("Failed to copy output device identifier.");
return CUBEB_ERROR; return CUBEB_ERROR;
@ -2512,7 +2526,7 @@ setup_wasapi_stream(cubeb_stream * stm)
cubeb_devid matched = wasapi_find_bt_handsfree_output_device(stm); cubeb_devid matched = wasapi_find_bt_handsfree_output_device(stm);
if (matched) { if (matched) {
selected_output_device_id = 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; stm->output_stream_params.layout = stm->input_stream_params.layout;
if (stm->input_device_id) { if (stm->input_device_id) {
if (std::unique_ptr<wchar_t[]> tmp = 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); XASSERT(!selected_output_device_id);
selected_output_device_id = move(tmp); selected_output_device_id = std::move(tmp);
} else { } else {
LOG("Failed to copy device identifier while copying input stream " LOG("Failed to copy device identifier while copying input stream "
"configuration to output stream configuration to drive loopback."); "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; return CUBEB_ERROR_INVALID_FORMAT;
} }
std::unique_ptr<cubeb_stream, decltype(&wasapi_stream_destroy)> stm( cubeb_stream * stm = new cubeb_stream();
new cubeb_stream(), wasapi_stream_destroy); auto_stream_ref stream_ref(stm);
stm->context = context; stm->context = context;
stm->data_callback = data_callback; stm->data_callback = data_callback;
@ -2763,12 +2777,24 @@ wasapi_stream_init(cubeb * context, cubeb_stream ** stream,
return CUBEB_ERROR; 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 /* Locking here is not strictly necessary, because we don't have a
notification client that can reset the stream yet, but it lets us notification client that can reset the stream yet, but it lets us
assert that the lock is held in the function. */ assert that the lock is held in the function. */
auto_lock lock(stm->stream_reset_lock); auto_lock lock(stm->stream_reset_lock);
rv = setup_wasapi_stream(stm.get()); rv = setup_wasapi_stream(stm);
} }
if (rv != CUBEB_OK) { if (rv != CUBEB_OK) {
return rv; return rv;
@ -2783,7 +2809,7 @@ wasapi_stream_init(cubeb * context, cubeb_stream ** stream,
!(output_stream_params->prefs & !(output_stream_params->prefs &
CUBEB_STREAM_PREF_DISABLE_DEVICE_SWITCHING))) { CUBEB_STREAM_PREF_DISABLE_DEVICE_SWITCHING))) {
LOG("Follow the system default input or/and output devices"); 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)) { if (FAILED(hr)) {
/* this is not fatal, we can still play audio, but we won't be able /* 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. */ 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; return CUBEB_OK;
} }
@ -2804,20 +2846,18 @@ close_wasapi_stream(cubeb_stream * stm)
stm->stream_reset_lock.assert_current_thread_owns(); 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 #ifdef CUBEB_WASAPI_USE_IAUDIOSTREAMVOLUME
stm->audio_stream_volume = nullptr; stm->audio_stream_volume = nullptr;
#endif #endif
stm->audio_clock = nullptr; 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>( stm->total_frames_written += static_cast<UINT64>(
round(stm->frames_written * round(stm->frames_written *
stream_to_mix_samplerate_ratio(stm->output_stream_params, 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 void
wasapi_stream_destroy(cubeb_stream * stm) wasapi_stream_destroy(cubeb_stream * stm)
{ {
XASSERT(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)) { stop_and_join_render_thread(stm);
// Emergency bailout: render thread becomes responsible for calling wasapi_stream_release(stm);
// 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;
} }
enum StreamDirection { OUTPUT, INPUT }; enum StreamDirection { OUTPUT, INPUT };
@ -2866,6 +2933,7 @@ enum StreamDirection { OUTPUT, INPUT };
int int
stream_start_one_side(cubeb_stream * stm, StreamDirection dir) stream_start_one_side(cubeb_stream * stm, StreamDirection dir)
{ {
XASSERT(stm);
XASSERT((dir == OUTPUT && stm->output_client) || XASSERT((dir == OUTPUT && stm->output_client) ||
(dir == INPUT && stm->input_client)); (dir == INPUT && stm->input_client));
@ -2909,7 +2977,7 @@ wasapi_stream_start(cubeb_stream * stm)
{ {
auto_lock lock(stm->stream_reset_lock); auto_lock lock(stm->stream_reset_lock);
XASSERT(stm && !stm->thread && !stm->shutdown_event); XASSERT(stm);
XASSERT(stm->output_client || stm->input_client); XASSERT(stm->output_client || stm->input_client);
if (stm->output_client) { if (stm->output_client) {
@ -2926,24 +2994,9 @@ wasapi_stream_start(cubeb_stream * stm)
} }
} }
stm->shutdown_event = CreateEvent(NULL, 0, 0, NULL); stm->active = true;
if (!stm->shutdown_event) {
LOG("Can't create the shutdown event, error: %lx", GetLastError());
return CUBEB_ERROR;
}
cubeb_async_log_reset_threads(); stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STARTED);
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);
return CUBEB_OK; 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)) { wasapi_state_callback(stm, stm->user_ptr, CUBEB_STATE_STOPPED);
// 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;
} }
return CUBEB_OK; return CUBEB_OK;
@ -3114,8 +3163,9 @@ wstr_to_utf8(LPCWSTR str)
return ret; return ret;
} }
static std::unique_ptr<wchar_t const []> static std::unique_ptr<wchar_t const[]>
utf8_to_wstr(char const * str) { utf8_to_wstr(char const * str)
{
int size = ::MultiByteToWideChar(CP_UTF8, 0, str, -1, nullptr, 0); int size = ::MultiByteToWideChar(CP_UTF8, 0, str, -1, nullptr, 0);
if (size <= 0) { if (size <= 0) {
return nullptr; return nullptr;
@ -3126,8 +3176,8 @@ utf8_to_wstr(char const * str) {
return ret; return ret;
} }
static com_ptr<IMMDevice> wasapi_get_device_node( static com_ptr<IMMDevice>
IMMDeviceEnumerator * enumerator, IMMDevice * dev) wasapi_get_device_node(IMMDeviceEnumerator * enumerator, IMMDevice * dev)
{ {
com_ptr<IMMDevice> ret; com_ptr<IMMDevice> ret;
com_ptr<IDeviceTopology> devtopo; com_ptr<IDeviceTopology> devtopo;

View File

@ -105,10 +105,13 @@ struct cubeb_stream {
int free_buffers; int free_buffers;
int shutdown; int shutdown;
int draining; int draining;
int error;
HANDLE event; HANDLE event;
HWAVEOUT waveout; HWAVEOUT waveout;
CRITICAL_SECTION lock; CRITICAL_SECTION lock;
uint64_t written; uint64_t written;
/* number of frames written during preroll */
uint64_t position_base;
float soft_volume; float soft_volume;
/* For position wrap-around handling: */ /* For position wrap-around handling: */
size_t frame_size; size_t frame_size;
@ -150,6 +153,14 @@ winmm_get_next_buffer(cubeb_stream * stm)
return hdr; 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 static void
winmm_refill_stream(cubeb_stream * stm) winmm_refill_stream(cubeb_stream * stm)
{ {
@ -158,13 +169,20 @@ winmm_refill_stream(cubeb_stream * stm)
long wanted; long wanted;
MMRESULT r; MMRESULT r;
ALOG("winmm_refill_stream");
EnterCriticalSection(&stm->lock); EnterCriticalSection(&stm->lock);
if (stm->error) {
LeaveCriticalSection(&stm->lock);
return;
}
stm->free_buffers += 1; stm->free_buffers += 1;
XASSERT(stm->free_buffers > 0 && stm->free_buffers <= NBUFS); XASSERT(stm->free_buffers > 0 && stm->free_buffers <= NBUFS);
if (stm->draining) { if (stm->draining) {
LeaveCriticalSection(&stm->lock); LeaveCriticalSection(&stm->lock);
if (stm->free_buffers == NBUFS) { if (stm->free_buffers == NBUFS) {
ALOG("winmm_refill_stream draining");
stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_DRAINED); stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_DRAINED);
} }
SetEvent(stm->event); 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); got = stm->data_callback(stm, stm->user_ptr, NULL, hdr->lpData, wanted);
EnterCriticalSection(&stm->lock); EnterCriticalSection(&stm->lock);
if (got < 0) { if (got < 0) {
stm->error = 1;
LeaveCriticalSection(&stm->lock); LeaveCriticalSection(&stm->lock);
/* XXX handle this case */ SetEvent(stm->event);
XASSERT(0); stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR);
return; return;
} else if (got < wanted) { } else if (got < wanted) {
stm->draining = 1; stm->draining = 1;
@ -224,6 +243,8 @@ winmm_refill_stream(cubeb_stream * stm)
return; return;
} }
ALOG("winmm_refill_stream %ld frames", got);
LeaveCriticalSection(&stm->lock); LeaveCriticalSection(&stm->lock);
} }
@ -486,7 +507,11 @@ winmm_stream_init(cubeb * context, cubeb_stream ** stream,
stm->params = *output_stream_params; 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->state_callback = state_callback;
stm->user_ptr = user_ptr; stm->user_ptr = user_ptr;
stm->written = 0; stm->written = 0;
@ -553,9 +578,18 @@ winmm_stream_init(cubeb * context, cubeb_stream ** stream,
stm->frame_size = bytes_per_frame(stm->params); stm->frame_size = bytes_per_frame(stm->params);
stm->prev_pos_lo_dword = 0; stm->prev_pos_lo_dword = 0;
stm->pos_hi_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; *stream = stm;
LOG("winmm_stream_init OK");
return CUBEB_OK; return CUBEB_OK;
} }
@ -585,7 +619,7 @@ winmm_stream_destroy(cubeb_stream * stm)
LeaveCriticalSection(&stm->lock); LeaveCriticalSection(&stm->lock);
/* Wait for all blocks to complete. */ /* Wait for all blocks to complete. */
while (device_valid && enqueued > 0) { while (device_valid && enqueued > 0 && !stm->error) {
DWORD rv = WaitForSingleObject(stm->event, INFINITE); DWORD rv = WaitForSingleObject(stm->event, INFINITE);
XASSERT(rv == WAIT_OBJECT_0); XASSERT(rv == WAIT_OBJECT_0);
@ -774,7 +808,17 @@ winmm_stream_get_position(cubeb_stream * stm, uint64_t * position)
return CUBEB_ERROR; 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); LeaveCriticalSection(&stm->lock);
return CUBEB_OK; return CUBEB_OK;
@ -787,17 +831,12 @@ winmm_stream_get_latency(cubeb_stream * stm, uint32_t * latency)
MMTIME time; MMTIME time;
uint64_t written, position; uint64_t written, position;
EnterCriticalSection(&stm->lock); int rv = winmm_stream_get_position(stm, &position);
/* See the long comment above for why not just use TIME_SAMPLES here. */ if (rv != CUBEB_OK) {
time.wType = TIME_BYTES; return rv;
r = waveOutGetPosition(stm->waveout, &time, sizeof(time));
if (r != MMSYSERR_NOERROR || time.wType != TIME_BYTES) {
LeaveCriticalSection(&stm->lock);
return CUBEB_ERROR;
} }
position = update_64bit_position(stm, time.u.cb); EnterCriticalSection(&stm->lock);
written = stm->written; written = stm->written;
LeaveCriticalSection(&stm->lock); LeaveCriticalSection(&stm->lock);

View File

@ -22,7 +22,8 @@
"modules/21-libbacktrace.json", "modules/21-libbacktrace.json",
{ {
"name": "duckstation", "name": "duckstation",
"buildsystem": "cmake", "buildsystem": "cmake-ninja",
"no-make-install": true,
"build-options": { "build-options": {
"strip": false, "strip": false,
"no-debuginfo": true, "no-debuginfo": true,