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.
cmake_minimum_required(VERSION 3.14 FATAL_ERROR)
project(cubeb
VERSION 0.0.0)
project(cubeb C CXX)
option(BUILD_RUST_LIBS "Build rust backends" OFF)
option(LAZY_LOAD_LIBS "Lazily load shared libraries" ON)
if(NOT CMAKE_BUILD_TYPE)
@ -14,25 +12,17 @@ if(NOT CMAKE_BUILD_TYPE)
endif()
set(CMAKE_C_STANDARD 99)
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
if (BUILD_RUST_LIBS)
if(EXISTS "${PROJECT_SOURCE_DIR}/src/cubeb-pulse-rs")
set(USE_PULSE_RUST 1)
endif()
if(EXISTS "${PROJECT_SOURCE_DIR}/src/cubeb-coreaudio-rs")
set(USE_AUDIOUNIT_RUST 1)
endif()
endif()
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
set(CMAKE_CXX_WARNING_LEVEL 4)
if(NOT MSVC)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra -Wno-unused-parameter")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Wno-unused-parameter -fno-exceptions -fno-rtti")
else()
string(REPLACE "/GR" "" CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS}) # Disable RTTI
string(REPLACE "/EHsc" "" CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS}) # Disable Exceptions
string(REPLACE "/GR" "" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}") # Disable RTTI
string(REPLACE "/EHsc" "" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}") # Disable Exceptions
endif()
add_library(cubeb
@ -46,43 +36,15 @@ add_library(cubeb
target_include_directories(cubeb
PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include> $<INSTALL_INTERFACE:include>
)
set_target_properties(cubeb PROPERTIES
VERSION ${cubeb_VERSION}
SOVERSION ${cubeb_VERSION_MAJOR}
)
include(CMakePackageConfigHelpers)
write_basic_package_version_file(
"${PROJECT_BINARY_DIR}/${PROJECT_NAME}ConfigVersion.cmake"
COMPATIBILITY SameMajorVersion
)
configure_package_config_file(
"Config.cmake.in"
"${PROJECT_BINARY_DIR}/${PROJECT_NAME}Config.cmake"
INSTALL_DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}"
)
install(
FILES "${PROJECT_BINARY_DIR}/${PROJECT_NAME}Config.cmake" "${PROJECT_BINARY_DIR}/${PROJECT_NAME}ConfigVersion.cmake"
DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}"
)
install(TARGETS cubeb EXPORT "${PROJECT_NAME}Targets")
install(
EXPORT "${PROJECT_NAME}Targets"
NAMESPACE "${PROJECT_NAME}::"
DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}"
)
add_library(speex OBJECT subprojects/speex/resample.c)
set_target_properties(speex PROPERTIES POSITION_INDEPENDENT_CODE TRUE)
target_include_directories(speex INTERFACE subprojects)
target_compile_definitions(speex PUBLIC
OUTSIDE_SPEEX
FLOATING_POINT
EXPORT=
RANDOM_PREFIX=speex
OUTSIDE_SPEEX
FLOATING_POINT
EXPORT=
RANDOM_PREFIX=speex
)
# $<BUILD_INTERFACE:> required because of https://gitlab.kitware.com/cmake/cmake/-/issues/15415
@ -96,17 +58,19 @@ find_package(Threads)
target_link_libraries(cubeb PRIVATE Threads::Threads)
if(LAZY_LOAD_LIBS)
check_include_files(pulse/pulseaudio.h USE_PULSE)
check_include_files(alsa/asoundlib.h USE_ALSA)
check_include_files(jack/jack.h USE_JACK)
check_include_files(sndio.h USE_SNDIO)
check_include_files(aaudio/AAudio.h USE_AAUDIO)
if(NOT APPLE AND NOT WIN32)
# Skip checks on MacOS because it takes ages in XCode.
check_include_files(pulse/pulseaudio.h USE_PULSE)
check_include_files(alsa/asoundlib.h USE_ALSA)
check_include_files(jack/jack.h USE_JACK)
check_include_files(sndio.h USE_SNDIO)
if(USE_PULSE OR USE_ALSA OR USE_JACK OR USE_SNDIO OR USE_AAUDIO)
target_link_libraries(cubeb PRIVATE ${CMAKE_DL_LIBS})
if(USE_PULSE OR USE_ALSA OR USE_JACK OR USE_SNDIO)
target_link_libraries(cubeb PRIVATE ${CMAKE_DL_LIBS})
endif()
endif()
else()
elseif(NOT APPLE AND NOT WIN32)
find_package(PkgConfig REQUIRED)
@ -136,12 +100,6 @@ else()
target_compile_definitions(cubeb PRIVATE DISABLE_LIBSNDIO_DLOPEN)
target_link_libraries(cubeb PRIVATE sndio)
endif()
check_include_files(aaudio/AAudio.h USE_AAUDIO)
if(USE_AAUDIO)
target_compile_definitions(cubeb PRIVATE DISABLE_LIBAAUDIO_DLOPEN)
target_link_libraries(cubeb PRIVATE aaudio)
endif()
endif()
if(USE_PULSE)
@ -164,138 +122,57 @@ if(USE_SNDIO)
target_compile_definitions(cubeb PRIVATE USE_SNDIO)
endif()
if(USE_AAUDIO)
target_sources(cubeb PRIVATE src/cubeb_aaudio.cpp)
target_compile_definitions(cubeb PRIVATE USE_AAUDIO)
# set this definition to enable low latency mode. Possibly bad for battery
target_compile_definitions(cubeb PRIVATE CUBEB_AAUDIO_LOW_LATENCY)
# set this definition to enable power saving mode. Possibly resulting
# in high latency
# target_compile_definitions(cubeb PRIVATE CUBEB_AAUDIO_LOW_POWER_SAVING)
# set this mode to make the backend use an exclusive stream.
# will decrease latency.
# target_compile_definitions(cubeb PRIVATE CUBEB_AAUDIO_EXCLUSIVE_STREAM)
endif()
check_include_files(AudioUnit/AudioUnit.h USE_AUDIOUNIT)
if(USE_AUDIOUNIT)
target_sources(cubeb PRIVATE
src/cubeb_audiounit.cpp
src/cubeb_osx_run_loop.cpp)
target_compile_definitions(cubeb PRIVATE USE_AUDIOUNIT)
target_link_libraries(cubeb PRIVATE "-framework AudioUnit" "-framework CoreAudio" "-framework CoreServices")
endif()
check_include_files(audioclient.h USE_WASAPI)
if(USE_WASAPI)
target_sources(cubeb PRIVATE
src/cubeb_wasapi.cpp)
target_compile_definitions(cubeb PRIVATE USE_WASAPI)
target_link_libraries(cubeb PRIVATE avrt ole32 ksuser)
endif()
check_include_files("windows.h;mmsystem.h" USE_WINMM)
if(USE_WINMM)
target_sources(cubeb PRIVATE
src/cubeb_winmm.c)
target_compile_definitions(cubeb PRIVATE USE_WINMM)
target_link_libraries(cubeb PRIVATE winmm)
endif()
check_include_files(SLES/OpenSLES.h USE_OPENSL)
if(USE_OPENSL)
target_sources(cubeb PRIVATE
src/cubeb_opensl.c
src/cubeb-jni.cpp)
target_compile_definitions(cubeb PRIVATE USE_OPENSL)
target_link_libraries(cubeb PRIVATE OpenSLES)
endif()
check_include_files(sys/soundcard.h HAVE_SYS_SOUNDCARD_H)
if(HAVE_SYS_SOUNDCARD_H)
try_compile(USE_OSS "${PROJECT_BINARY_DIR}/compile_tests"
${PROJECT_SOURCE_DIR}/cmake/compile_tests/oss_is_v4.c)
if(USE_OSS)
# strlcpy is not available on BSD systems that use glibc,
# like Debian kfreebsd, so try using libbsd if available
include(CheckSymbolExists)
check_symbol_exists(strlcpy string.h HAVE_STRLCPY)
if(NOT HAVE_STRLCPY)
pkg_check_modules(libbsd-overlay IMPORTED_TARGET libbsd-overlay)
if(libbsd-overlay_FOUND)
target_link_libraries(cubeb PRIVATE PkgConfig::libbsd-overlay)
set(HAVE_STRLCPY true)
endif()
endif()
if (HAVE_STRLCPY)
target_sources(cubeb PRIVATE
src/cubeb_oss.c)
target_compile_definitions(cubeb PRIVATE USE_OSS)
endif()
if(APPLE)
check_include_files(AudioUnit/AudioUnit.h USE_AUDIOUNIT)
if(USE_AUDIOUNIT)
target_sources(cubeb PRIVATE
src/cubeb_audiounit.cpp
src/cubeb_osx_run_loop.cpp)
target_compile_definitions(cubeb PRIVATE USE_AUDIOUNIT)
target_link_libraries(cubeb PRIVATE "-framework AudioUnit" "-framework CoreAudio" "-framework CoreServices")
endif()
endif()
check_include_files(android/log.h USE_AUDIOTRACK)
if(USE_AUDIOTRACK)
target_sources(cubeb PRIVATE
src/cubeb_audiotrack.c)
target_compile_definitions(cubeb PRIVATE USE_AUDIOTRACK)
target_link_libraries(cubeb PRIVATE log)
if(WIN32)
check_include_files(audioclient.h USE_WASAPI)
if(USE_WASAPI)
target_sources(cubeb PRIVATE
src/cubeb_wasapi.cpp)
target_compile_definitions(cubeb PRIVATE USE_WASAPI)
target_link_libraries(cubeb PRIVATE avrt ole32 ksuser)
endif()
check_include_files("windows.h;mmsystem.h" USE_WINMM)
if(USE_WINMM)
target_sources(cubeb PRIVATE
src/cubeb_winmm.c)
target_compile_definitions(cubeb PRIVATE USE_WINMM)
target_link_libraries(cubeb PRIVATE winmm)
endif()
endif()
check_include_files(sys/audioio.h USE_SUN)
if(USE_SUN)
target_sources(cubeb PRIVATE
src/cubeb_sun.c)
target_compile_definitions(cubeb PRIVATE USE_SUN)
if(NOT WIN32 AND NOT APPLE)
check_include_files(sys/soundcard.h HAVE_SYS_SOUNDCARD_H)
if(HAVE_SYS_SOUNDCARD_H)
try_compile(USE_OSS "${PROJECT_BINARY_DIR}/compile_tests"
${PROJECT_SOURCE_DIR}/cmake/compile_tests/oss_is_v4.c)
if(USE_OSS)
# strlcpy is not available on BSD systems that use glibc,
# like Debian kfreebsd, so try using libbsd if available
include(CheckSymbolExists)
check_symbol_exists(strlcpy string.h HAVE_STRLCPY)
if(NOT HAVE_STRLCPY)
pkg_check_modules(libbsd-overlay IMPORTED_TARGET libbsd-overlay)
if(libbsd-overlay_FOUND)
target_link_libraries(cubeb PRIVATE PkgConfig::libbsd-overlay)
set(HAVE_STRLCPY true)
endif()
endif()
if (HAVE_STRLCPY)
target_sources(cubeb PRIVATE
src/cubeb_oss.c)
target_compile_definitions(cubeb PRIVATE USE_OSS)
endif()
endif()
endif()
endif()
check_include_files(kai.h USE_KAI)
if(USE_KAI)
target_sources(cubeb PRIVATE
src/cubeb_kai.c)
target_compile_definitions(cubeb PRIVATE USE_KAI)
target_link_libraries(cubeb PRIVATE kai)
endif()
if(USE_PULSE AND USE_PULSE_RUST)
include(ExternalProject)
set_directory_properties(PROPERTIES EP_PREFIX ${CMAKE_BINARY_DIR}/rust)
ExternalProject_Add(
cubeb_pulse_rs
DOWNLOAD_COMMAND ""
CONFIGURE_COMMAND ""
BUILD_COMMAND cargo build COMMAND cargo build --release
BUILD_ALWAYS ON
BINARY_DIR "${PROJECT_SOURCE_DIR}/src/cubeb-pulse-rs"
INSTALL_COMMAND ""
LOG_BUILD ON)
add_dependencies(cubeb cubeb_pulse_rs)
target_compile_definitions(cubeb PRIVATE USE_PULSE_RUST)
target_link_libraries(cubeb PRIVATE
debug "${PROJECT_SOURCE_DIR}/src/cubeb-pulse-rs/target/debug/libcubeb_pulse.a"
optimized "${PROJECT_SOURCE_DIR}/src/cubeb-pulse-rs/target/release/libcubeb_pulse.a" pulse)
endif()
if(USE_AUDIOUNIT AND USE_AUDIOUNIT_RUST)
include(ExternalProject)
set_directory_properties(PROPERTIES EP_PREFIX ${CMAKE_BINARY_DIR}/rust)
ExternalProject_Add(
cubeb_coreaudio_rs
DOWNLOAD_COMMAND ""
CONFIGURE_COMMAND ""
BUILD_COMMAND cargo build COMMAND cargo build --release
BUILD_ALWAYS ON
BINARY_DIR "${PROJECT_SOURCE_DIR}/src/cubeb-coreaudio-rs"
INSTALL_COMMAND ""
LOG_BUILD ON)
add_dependencies(cubeb cubeb_coreaudio_rs)
target_compile_definitions(cubeb PRIVATE USE_AUDIOUNIT_RUST)
target_link_libraries(cubeb PRIVATE
debug "${PROJECT_SOURCE_DIR}/src/cubeb-coreaudio-rs/target/debug/libcubeb_coreaudio.a"
optimized "${PROJECT_SOURCE_DIR}/src/cubeb-coreaudio-rs/target/release/libcubeb_coreaudio.a")
endif()

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="src\cubeb-internal.h" />
<ClInclude Include="src\cubeb-speex-resampler.h" />
<ClInclude Include="src\cubeb_array_queue.h" />
<ClInclude Include="src\cubeb_assert.h" />
<ClInclude Include="src\cubeb_log.h" />
<ClInclude Include="src\cubeb_mixer.h" />

View file

@ -5,7 +5,6 @@
<ClInclude Include="include\cubeb\cubeb_export.h" />
<ClInclude Include="src\cubeb-internal.h" />
<ClInclude Include="src\cubeb-speex-resampler.h" />
<ClInclude Include="src\cubeb_array_queue.h" />
<ClInclude Include="src\cubeb_assert.h" />
<ClInclude Include="src\cubeb_log.h" />
<ClInclude Include="src\cubeb_mixer.h" />

View file

@ -163,6 +163,7 @@ typedef enum {
implications. */
} cubeb_log_level;
/// A single channel position, to be used in a bitmask.
typedef enum {
CHANNEL_UNKNOWN = 0,
CHANNEL_FRONT_LEFT = 1 << 0,
@ -185,6 +186,9 @@ typedef enum {
CHANNEL_TOP_BACK_RIGHT = 1 << 17
} cubeb_channel;
/// A bitmask representing the channel layout of a cubeb stream. This is
/// bit-compatible with WAVEFORMATEXENSIBLE and in the same order as the SMPTE
/// ordering.
typedef uint32_t cubeb_channel_layout;
// Some common layout definitions.
enum {
@ -433,7 +437,7 @@ typedef void (*cubeb_state_callback)(cubeb_stream * stream, void * user_ptr,
/**
* User supplied callback called when the underlying device changed.
* @param user The pointer passed to cubeb_stream_init. */
* @param user_ptr The pointer passed to cubeb_stream_init. */
typedef void (*cubeb_device_changed_callback)(void * user_ptr);
/**

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;
#ifdef __cplusplus
extern "C" {
#endif
cubeb_jni *
cubeb_jni_init();
int
@ -10,4 +14,8 @@ cubeb_get_output_latency_from_jni(cubeb_jni * cubeb_jni_ptr);
void
cubeb_jni_destroy(cubeb_jni * cubeb_jni_ptr);
#ifdef __cplusplus
};
#endif
#endif // _CUBEB_JNI_H_

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

File diff suppressed because it is too large Load diff

View file

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

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

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>
#endif
cubeb_log_level g_cubeb_log_level;
cubeb_log_callback g_cubeb_log_callback;
static std::atomic<cubeb_log_level> g_cubeb_log_level;
static std::atomic<cubeb_log_callback> g_cubeb_log_callback;
/** The maximum size of a log message, after having been formatted. */
const size_t CUBEB_LOG_MESSAGE_MAX_SIZE = 256;
@ -25,111 +25,56 @@ const size_t CUBEB_LOG_MESSAGE_MAX_SIZE = 256;
* messages. */
const size_t CUBEB_LOG_MESSAGE_QUEUE_DEPTH = 40;
/** Number of milliseconds to wait before dequeuing log messages. */
#define CUBEB_LOG_BATCH_PRINT_INTERVAL_MS 10
/**
* This wraps an inline buffer, that represents a log message, that must be
* null-terminated.
* This class should not use system calls or other potentially blocking code.
*/
class cubeb_log_message {
public:
cubeb_log_message() { *storage = '\0'; }
cubeb_log_message(char const str[CUBEB_LOG_MESSAGE_MAX_SIZE])
{
size_t length = strlen(str);
/* paranoia against malformed message */
assert(length < CUBEB_LOG_MESSAGE_MAX_SIZE);
if (length > CUBEB_LOG_MESSAGE_MAX_SIZE - 1) {
return;
}
PodCopy(storage, str, length);
storage[length] = '\0';
}
char const * get() { return storage; }
private:
char storage[CUBEB_LOG_MESSAGE_MAX_SIZE];
};
/** Lock-free asynchronous logger, made so that logging from a
* real-time audio callback does not block the audio thread. */
class cubeb_async_logger {
public:
/* This is thread-safe since C++11 */
static cubeb_async_logger & get()
{
static cubeb_async_logger instance;
return instance;
}
void push(char const str[CUBEB_LOG_MESSAGE_MAX_SIZE])
{
cubeb_log_message msg(str);
msg_queue.enqueue(msg);
}
void run()
{
std::thread([this]() {
CUBEB_REGISTER_THREAD("cubeb_log");
while (true) {
cubeb_log_message msg;
while (msg_queue.dequeue(&msg, 1)) {
LOG_INTERNAL_NO_FORMAT(CUBEB_LOG_NORMAL, "%s", msg.get());
}
#ifdef _WIN32
Sleep(CUBEB_LOG_BATCH_PRINT_INTERVAL_MS);
#else
timespec sleep_duration = sleep_for;
timespec remainder;
do {
if (nanosleep(&sleep_duration, &remainder) == 0 || errno != EINTR) {
break;
}
sleep_duration = remainder;
} while (remainder.tv_sec || remainder.tv_nsec);
#endif
}
CUBEB_UNREGISTER_THREAD();
}).detach();
}
// Tell the underlying queue the producer thread has changed, so it does not
// assert in debug. This should be called with the thread stopped.
void reset_producer_thread() { msg_queue.reset_thread_ids(); }
private:
#ifndef _WIN32
const struct timespec sleep_for = {
CUBEB_LOG_BATCH_PRINT_INTERVAL_MS / 1000,
(CUBEB_LOG_BATCH_PRINT_INTERVAL_MS % 1000) * 1000 * 1000};
#endif
cubeb_async_logger() : msg_queue(CUBEB_LOG_MESSAGE_QUEUE_DEPTH) { run(); }
/** This is quite a big data structure, but is only instantiated if the
* asynchronous logger is used.*/
lock_free_queue<cubeb_log_message> msg_queue;
};
const size_t CUBEB_LOG_BATCH_PRINT_INTERVAL_MS = 10;
void
cubeb_async_log(char const * fmt, ...)
cubeb_noop_log_callback(char const * /* fmt */, ...)
{
}
void
cubeb_log_internal(char const * file, uint32_t line, char const * fmt, ...)
{
if (!g_cubeb_log_callback) {
return;
}
// This is going to copy a 256 bytes array around, which is fine.
// We don't want to allocate memory here, because this is made to
// be called from a real-time callback.
va_list args;
va_start(args, fmt);
char msg[CUBEB_LOG_MESSAGE_MAX_SIZE];
vsnprintf(msg, CUBEB_LOG_MESSAGE_MAX_SIZE, fmt, args);
cubeb_async_logger::get().push(msg);
va_end(args);
g_cubeb_log_callback.load()("%s:%d:%s", file, line, msg);
}
void
cubeb_async_log_reset_threads(void)
cubeb_log_internal_no_format(const char * msg)
{
if (!g_cubeb_log_callback) {
return;
}
cubeb_async_logger::get().reset_producer_thread();
g_cubeb_log_callback.load()(msg);
}
void
cubeb_log_set(cubeb_log_level log_level, cubeb_log_callback log_callback)
{
g_cubeb_log_level = log_level;
// Once a callback has a been set, `g_cubeb_log_callback` is never set back to
// nullptr, to prevent a TOCTOU race between checking the pointer
if (log_callback && log_level != CUBEB_LOG_DISABLED) {
g_cubeb_log_callback = log_callback;
} else if (!log_callback || CUBEB_LOG_DISABLED) {
g_cubeb_log_callback = cubeb_noop_log_callback;
} else {
assert(false && "Incorrect parameters passed to cubeb_log_set");
}
}
cubeb_log_level
cubeb_log_get_level()
{
return g_cubeb_log_level;
}
cubeb_log_callback
cubeb_log_get_callback()
{
if (g_cubeb_log_callback == cubeb_noop_log_callback) {
return nullptr;
}
return g_cubeb_log_callback;
}

View file

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

View file

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

File diff suppressed because it is too large Load diff

View file

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

View file

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

View file

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

View file

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

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 <windef.h>
#include <windows.h>
/* clang-format off */
/* These need to be included after windows.h */
#include <mmsystem.h>
/* clang-format on */
#include "cubeb-internal.h"
#include "cubeb/cubeb.h"
#include "cubeb_mixer.h"
#include "cubeb_resampler.h"
#include "cubeb_strings.h"
#include "cubeb_tracing.h"
#include "cubeb_utils.h"
// Windows 10 exposes the IAudioClient3 interface to create low-latency streams.
@ -96,6 +101,8 @@ namespace {
const int64_t LATENCY_NOT_AVAILABLE_YET = -1;
const DWORD DEVICE_CHANGE_DEBOUNCE_MS = 250;
struct com_heap_ptr_deleter {
void operator()(void * ptr) const noexcept { CoTaskMemFree(ptr); }
};
@ -103,7 +110,9 @@ struct com_heap_ptr_deleter {
template <typename T>
using com_heap_ptr = std::unique_ptr<T, com_heap_ptr_deleter>;
template <typename T, size_t N> constexpr size_t ARRAY_LENGTH(T (&)[N])
template <typename T, size_t N>
constexpr size_t
ARRAY_LENGTH(T (&)[N])
{
return N;
}
@ -181,6 +190,20 @@ private:
T * ptr = nullptr;
};
LONG
wasapi_stream_add_ref(cubeb_stream * stm);
LONG
wasapi_stream_release(cubeb_stream * stm);
struct auto_stream_ref {
auto_stream_ref(cubeb_stream * stm_) : stm(stm_)
{
wasapi_stream_add_ref(stm);
}
~auto_stream_ref() { wasapi_stream_release(stm); }
cubeb_stream * stm;
};
extern cubeb_ops const wasapi_ops;
static com_heap_ptr<wchar_t>
@ -223,6 +246,11 @@ private:
com_heap_ptr<wchar_t> capture_comms_id;
};
struct AutoRegisterThread {
AutoRegisterThread(const char * name) { CUBEB_REGISTER_THREAD(name); }
~AutoRegisterThread() { CUBEB_UNREGISTER_THREAD(); }
};
int
wasapi_stream_stop(cubeb_stream * stm);
int
@ -365,8 +393,8 @@ struct cubeb_stream {
com_ptr<IAudioClient> input_client;
/* Interface to use the event driven capture interface */
com_ptr<IAudioCaptureClient> capture_client;
/* This event is set by the stream_stop and stream_destroy
function, so the render loop can exit properly. */
/* This event is set by the stream_destroy function, so the render loop can
exit properly. */
HANDLE shutdown_event = 0;
/* Set by OnDefaultDeviceChanged when a stream reconfiguration is required.
The reconfiguration is handled by the render loop thread. */
@ -410,17 +438,23 @@ struct cubeb_stream {
float volume = 1.0;
/* True if the stream is draining. */
bool draining = false;
/* If the render thread fails to stop, this is set to true and ownership of
* the stm is "leaked" to the render thread for later cleanup. */
std::atomic<bool> emergency_bailout{false};
/* This needs an active audio input stream to be known, and is updated in the
* first audio input callback. */
std::atomic<int64_t> input_latency_hns{LATENCY_NOT_AVAILABLE_YET};
/* Those attributes count the number of frames requested (resp. received) by
the OS, to be able to detect drifts. This is only used for logging for now. */
size_t total_input_frames = 0;
size_t total_output_frames = 0;
/* This is set by the render loop thread once it has obtained a reference to
* COM and this stream object. */
HANDLE thread_ready_event = 0;
/* Keep a ref count on this stream object. After both stream_destroy has been
* called and the render loop thread has exited, destroy this stream object.
*/
LONG ref_count = 0;
/* True if the stream is active, false if inactive. */
bool active = false;
};
class monitor_device_notifications {
@ -463,6 +497,7 @@ public:
private:
static unsigned int __stdcall thread_proc(LPVOID args)
{
AutoRegisterThread raii("WASAPI device notification thread");
XASSERT(args);
auto mdn = static_cast<monitor_device_notifications *>(args);
mdn->notification_thread_loop();
@ -689,7 +724,8 @@ public:
}
wasapi_endpoint_notification_client(HANDLE event, ERole role)
: ref_count(1), reconfigure_event(event), role(role)
: ref_count(1), reconfigure_event(event), role(role),
last_device_change(timeGetTime())
{
}
@ -698,17 +734,32 @@ public:
HRESULT STDMETHODCALLTYPE OnDefaultDeviceChanged(EDataFlow flow, ERole role,
LPCWSTR device_id)
{
LOG("endpoint: Audio device default changed.");
LOG("endpoint: Audio device default changed flow=%d role=%d "
"new_device_id=%ws.",
flow, role, device_id);
/* we only support a single stream type for now. */
if (flow != eRender && role != this->role) {
if (flow != eRender || role != this->role) {
return S_OK;
}
BOOL ok = SetEvent(reconfigure_event);
if (!ok) {
LOG("endpoint: SetEvent on reconfigure_event failed: %lx",
GetLastError());
DWORD last_change_ms = timeGetTime() - last_device_change;
bool same_device = default_device_id && device_id &&
wcscmp(default_device_id.get(), device_id) == 0;
LOG("endpoint: Audio device default changed last_change=%u same_device=%d",
last_change_ms, same_device);
if (last_change_ms > DEVICE_CHANGE_DEBOUNCE_MS || !same_device) {
if (device_id) {
default_device_id.reset(_wcsdup(device_id));
} else {
default_device_id.reset();
}
BOOL ok = SetEvent(reconfigure_event);
LOG("endpoint: Audio device default changed: trigger reconfig");
if (!ok) {
LOG("endpoint: SetEvent on reconfigure_event failed: %lx",
GetLastError());
}
}
return S_OK;
@ -747,6 +798,8 @@ private:
LONG ref_count;
HANDLE reconfigure_event;
ERole role;
std::unique_ptr<const wchar_t[]> default_device_id;
DWORD last_device_change;
};
namespace {
@ -756,9 +809,6 @@ wasapi_data_callback(cubeb_stream * stm, void * user_ptr,
void const * input_buffer, void * output_buffer,
long nframes)
{
if (stm->emergency_bailout) {
return CUBEB_ERROR;
}
return stm->data_callback(stm, user_ptr, input_buffer, output_buffer,
nframes);
}
@ -766,9 +816,6 @@ wasapi_data_callback(cubeb_stream * stm, void * user_ptr,
void
wasapi_state_callback(cubeb_stream * stm, void * user_ptr, cubeb_state state)
{
if (stm->emergency_bailout) {
return;
}
return stm->state_callback(stm, user_ptr, state);
}
@ -1303,8 +1350,10 @@ refill_callback_output(cubeb_stream * stm)
long got = refill(stm, nullptr, 0, output_buffer, output_frames);
ALOGV("Output callback: output frames requested: %Iu, got %ld", output_frames,
got);
if (got != output_frames) {
ALOGV("Output callback: output frames requested: %Iu, got %ld", output_frames,
got);
}
if (got < 0) {
return false;
}
@ -1322,29 +1371,12 @@ refill_callback_output(cubeb_stream * stm)
void
wasapi_stream_destroy(cubeb_stream * stm);
static void
handle_emergency_bailout(cubeb_stream * stm)
{
if (stm->emergency_bailout) {
CloseHandle(stm->thread);
stm->thread = NULL;
CloseHandle(stm->shutdown_event);
stm->shutdown_event = 0;
wasapi_stream_destroy(stm);
_endthreadex(0);
}
}
static unsigned int __stdcall wasapi_stream_render_loop(LPVOID stream)
{
AutoRegisterThread raii("cubeb rendering thread");
cubeb_stream * stm = static_cast<cubeb_stream *>(stream);
bool is_playing = true;
HANDLE wait_array[4] = {stm->shutdown_event, stm->reconfigure_event,
stm->refill_event, stm->input_available_event};
HANDLE mmcss_handle = NULL;
HRESULT hr = 0;
DWORD mmcss_task_index = 0;
auto_stream_ref stream_ref(stm);
struct auto_com {
auto_com()
{
@ -1354,6 +1386,21 @@ static unsigned int __stdcall wasapi_stream_render_loop(LPVOID stream)
~auto_com() { CoUninitialize(); }
} com;
bool is_playing = true;
HANDLE wait_array[4] = {stm->shutdown_event, stm->reconfigure_event,
stm->refill_event, stm->input_available_event};
HANDLE mmcss_handle = NULL;
HRESULT hr = 0;
DWORD mmcss_task_index = 0;
// Signal wasapi_stream_start that we've initialized COM and incremented
// the stream's ref_count.
BOOL ok = SetEvent(stm->thread_ready_event);
if (!ok) {
LOG("thread_ready SetEvent failed: %lx", GetLastError());
return 0;
}
/* We could consider using "Pro Audio" here for WebAudio and
maybe WebRTC. */
mmcss_handle = AvSetMmThreadCharacteristicsA("Audio", &mmcss_task_index);
@ -1363,20 +1410,9 @@ static unsigned int __stdcall wasapi_stream_render_loop(LPVOID stream)
GetLastError());
}
/* WaitForMultipleObjects timeout can trigger in cases where we don't want to
treat it as a timeout, such as across a system sleep/wake cycle. Trigger
the timeout error handling only when the timeout_limit is reached, which is
reset on each successful loop. */
unsigned timeout_count = 0;
const unsigned timeout_limit = 3;
while (is_playing) {
handle_emergency_bailout(stm);
DWORD waitResult = WaitForMultipleObjects(ARRAY_LENGTH(wait_array),
wait_array, FALSE, 1000);
handle_emergency_bailout(stm);
if (waitResult != WAIT_TIMEOUT) {
timeout_count = 0;
}
wait_array, FALSE, INFINITE);
switch (waitResult) {
case WAIT_OBJECT_0: { /* shutdown */
is_playing = false;
@ -1388,36 +1424,40 @@ static unsigned int __stdcall wasapi_stream_render_loop(LPVOID stream)
continue;
}
case WAIT_OBJECT_0 + 1: { /* reconfigure */
auto_lock lock(stm->stream_reset_lock);
if (!stm->active) {
/* Avoid reconfiguring, stream start will handle it. */
LOG("Stream is not active, ignoring reconfigure.");
continue;
}
XASSERT(stm->output_client || stm->input_client);
LOG("Reconfiguring the stream");
/* Close the stream */
bool was_running = false;
if (stm->output_client) {
stm->output_client->Stop();
was_running = stm->output_client->Stop() == S_OK;
LOG("Output stopped.");
}
if (stm->input_client) {
stm->input_client->Stop();
was_running = stm->input_client->Stop() == S_OK;
LOG("Input stopped.");
}
{
auto_lock lock(stm->stream_reset_lock);
close_wasapi_stream(stm);
LOG("Stream closed.");
/* Reopen a stream and start it immediately. This will automatically
pick the new default device for this role. */
int r = setup_wasapi_stream(stm);
if (r != CUBEB_OK) {
LOG("Error setting up the stream during reconfigure.");
/* Don't destroy the stream here, since we expect the caller to do
so after the error has propagated via the state callback. */
is_playing = false;
hr = E_FAIL;
continue;
}
LOG("Stream setup successfuly.");
close_wasapi_stream(stm);
LOG("Stream closed.");
/* Reopen a stream and start it immediately. This will automatically
pick the new default device for this role. */
int r = setup_wasapi_stream(stm);
if (r != CUBEB_OK) {
LOG("Error setting up the stream during reconfigure.");
/* Don't destroy the stream here, since we expect the caller to do
so after the error has propagated via the state callback. */
is_playing = false;
hr = E_FAIL;
continue;
}
LOG("Stream setup successfuly.");
XASSERT(stm->output_client || stm->input_client);
if (stm->output_client) {
if (was_running && stm->output_client) {
hr = stm->output_client->Start();
if (FAILED(hr)) {
LOG("Error starting output after reconfigure, error: %lx", hr);
@ -1426,7 +1466,7 @@ static unsigned int __stdcall wasapi_stream_render_loop(LPVOID stream)
}
LOG("Output started after reconfigure.");
}
if (stm->input_client) {
if (was_running && stm->input_client) {
hr = stm->input_client->Start();
if (FAILED(hr)) {
LOG("Error starting input after reconfiguring, error: %lx", hr);
@ -1455,14 +1495,6 @@ static unsigned int __stdcall wasapi_stream_render_loop(LPVOID stream)
break;
}
case WAIT_TIMEOUT:
XASSERT(stm->shutdown_event == wait_array[0]);
if (++timeout_count >= timeout_limit) {
LOG("Render loop reached the timeout limit.");
is_playing = false;
hr = E_FAIL;
}
break;
default:
LOG("case %lu not handled in render loop.", waitResult);
XASSERT(false);
@ -1482,8 +1514,6 @@ static unsigned int __stdcall wasapi_stream_render_loop(LPVOID stream)
AvRevertMmThreadCharacteristics(mmcss_handle);
}
handle_emergency_bailout(stm);
if (FAILED(hr)) {
wasapi_state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR);
}
@ -1739,24 +1769,16 @@ namespace {
enum ShutdownPhase { OnStop, OnDestroy };
bool
stop_and_join_render_thread(cubeb_stream * stm, ShutdownPhase phase)
stop_and_join_render_thread(cubeb_stream * stm)
{
// Only safe to transfer `stm` ownership to the render thread when
// the stream is being destroyed by the caller.
bool bailout = phase == OnDestroy;
LOG("%p: Stop and join render thread: %p (%d), phase=%d", stm, stm->thread,
stm->emergency_bailout.load(), static_cast<int>(phase));
LOG("%p: Stop and join render thread: %p", stm, stm->thread);
if (!stm->thread) {
return true;
}
XASSERT(!stm->emergency_bailout);
BOOL ok = SetEvent(stm->shutdown_event);
if (!ok) {
LOG("stop_and_join_render_thread: SetEvent failed: %lx", GetLastError());
stm->emergency_bailout = bailout;
return false;
}
@ -1774,31 +1796,23 @@ stop_and_join_render_thread(cubeb_stream * stm, ShutdownPhase phase)
LOG("stop_and_join_render_thread: WaitForSingleObject on thread failed: "
"%lx, %lx",
r, GetLastError());
stm->emergency_bailout = bailout;
return false;
}
// Only attempt to close and null out the thread and event if the
// WaitForSingleObject above succeeded.
LOG("stop_and_join_render_thread: Closing thread.");
CloseHandle(stm->thread);
stm->thread = NULL;
CloseHandle(stm->shutdown_event);
stm->shutdown_event = 0;
return true;
}
void
wasapi_destroy(cubeb * context)
{
auto_lock lock(context->lock);
XASSERT(!context->device_collection_enumerator &&
!context->collection_notification_client);
{
auto_lock lock(context->lock);
XASSERT(!context->device_collection_enumerator &&
!context->collection_notification_client);
if (context->device_ids) {
cubeb_strings_destroy(context->device_ids);
if (context->device_ids) {
cubeb_strings_destroy(context->device_ids);
}
}
delete context;
@ -2469,8 +2483,8 @@ setup_wasapi_stream(cubeb_stream * stm)
std::unique_ptr<const wchar_t[]> selected_output_device_id;
if (stm->output_device_id) {
if (std::unique_ptr<wchar_t[]> tmp =
move(copy_wide_string(stm->output_device_id.get()))) {
selected_output_device_id = move(tmp);
copy_wide_string(stm->output_device_id.get())) {
selected_output_device_id = std::move(tmp);
} else {
LOG("Failed to copy output device identifier.");
return CUBEB_ERROR;
@ -2512,7 +2526,7 @@ setup_wasapi_stream(cubeb_stream * stm)
cubeb_devid matched = wasapi_find_bt_handsfree_output_device(stm);
if (matched) {
selected_output_device_id =
move(utf8_to_wstr(reinterpret_cast<char const *>(matched)));
utf8_to_wstr(reinterpret_cast<char const *>(matched));
}
}
}
@ -2528,9 +2542,9 @@ setup_wasapi_stream(cubeb_stream * stm)
stm->output_stream_params.layout = stm->input_stream_params.layout;
if (stm->input_device_id) {
if (std::unique_ptr<wchar_t[]> tmp =
move(copy_wide_string(stm->input_device_id.get()))) {
copy_wide_string(stm->input_device_id.get())) {
XASSERT(!selected_output_device_id);
selected_output_device_id = move(tmp);
selected_output_device_id = std::move(tmp);
} else {
LOG("Failed to copy device identifier while copying input stream "
"configuration to output stream configuration to drive loopback.");
@ -2690,8 +2704,8 @@ wasapi_stream_init(cubeb * context, cubeb_stream ** stream,
return CUBEB_ERROR_INVALID_FORMAT;
}
std::unique_ptr<cubeb_stream, decltype(&wasapi_stream_destroy)> stm(
new cubeb_stream(), wasapi_stream_destroy);
cubeb_stream * stm = new cubeb_stream();
auto_stream_ref stream_ref(stm);
stm->context = context;
stm->data_callback = data_callback;
@ -2763,12 +2777,24 @@ wasapi_stream_init(cubeb * context, cubeb_stream ** stream,
return CUBEB_ERROR;
}
stm->shutdown_event = CreateEvent(NULL, 0, 0, NULL);
if (!stm->shutdown_event) {
LOG("Can't create the shutdown event, error: %lx", GetLastError());
return CUBEB_ERROR;
}
stm->thread_ready_event = CreateEvent(NULL, 0, 0, NULL);
if (!stm->thread_ready_event) {
LOG("Can't create the thread ready event, error: %lx", GetLastError());
return CUBEB_ERROR;
}
{
/* Locking here is not strictly necessary, because we don't have a
notification client that can reset the stream yet, but it lets us
assert that the lock is held in the function. */
auto_lock lock(stm->stream_reset_lock);
rv = setup_wasapi_stream(stm.get());
rv = setup_wasapi_stream(stm);
}
if (rv != CUBEB_OK) {
return rv;
@ -2783,7 +2809,7 @@ wasapi_stream_init(cubeb * context, cubeb_stream ** stream,
!(output_stream_params->prefs &
CUBEB_STREAM_PREF_DISABLE_DEVICE_SWITCHING))) {
LOG("Follow the system default input or/and output devices");
HRESULT hr = register_notification_client(stm.get());
HRESULT hr = register_notification_client(stm);
if (FAILED(hr)) {
/* this is not fatal, we can still play audio, but we won't be able
to keep using the default audio endpoint if it changes. */
@ -2791,9 +2817,25 @@ wasapi_stream_init(cubeb * context, cubeb_stream ** stream,
}
}
*stream = stm.release();
stm->thread =
(HANDLE)_beginthreadex(NULL, 512 * 1024, wasapi_stream_render_loop, stm,
STACK_SIZE_PARAM_IS_A_RESERVATION, NULL);
if (stm->thread == NULL) {
LOG("could not create WASAPI render thread.");
return CUBEB_ERROR;
}
LOG("Stream init succesfull (%p)", *stream);
// Wait for the wasapi_stream_render_loop thread to signal that COM has been
// initialized and the stream's ref_count has been incremented.
hr = WaitForSingleObject(stm->thread_ready_event, INFINITE);
XASSERT(hr == WAIT_OBJECT_0);
CloseHandle(stm->thread_ready_event);
stm->thread_ready_event = 0;
wasapi_stream_add_ref(stm);
*stream = stm;
LOG("Stream init successful (%p)", *stream);
return CUBEB_OK;
}
@ -2804,20 +2846,18 @@ close_wasapi_stream(cubeb_stream * stm)
stm->stream_reset_lock.assert_current_thread_owns();
stm->output_client = nullptr;
stm->render_client = nullptr;
stm->input_client = nullptr;
stm->capture_client = nullptr;
stm->output_device = nullptr;
stm->input_device = nullptr;
#ifdef CUBEB_WASAPI_USE_IAUDIOSTREAMVOLUME
stm->audio_stream_volume = nullptr;
#endif
stm->audio_clock = nullptr;
stm->render_client = nullptr;
stm->output_client = nullptr;
stm->output_device = nullptr;
stm->capture_client = nullptr;
stm->input_client = nullptr;
stm->input_device = nullptr;
stm->total_frames_written += static_cast<UINT64>(
round(stm->frames_written *
stream_to_mix_samplerate_ratio(stm->output_stream_params,
@ -2833,32 +2873,59 @@ close_wasapi_stream(cubeb_stream * stm)
}
}
LONG
wasapi_stream_add_ref(cubeb_stream * stm)
{
XASSERT(stm);
LONG result = InterlockedIncrement(&stm->ref_count);
LOGV("Stream ref count incremented = %i (%p)", result, stm);
return result;
}
LONG
wasapi_stream_release(cubeb_stream * stm)
{
XASSERT(stm);
LONG result = InterlockedDecrement(&stm->ref_count);
LOGV("Stream ref count decremented = %i (%p)", result, stm);
if (result == 0) {
LOG("Stream ref count hit zero, destroying (%p)", stm);
if (stm->notification_client) {
unregister_notification_client(stm);
}
CloseHandle(stm->shutdown_event);
CloseHandle(stm->reconfigure_event);
CloseHandle(stm->refill_event);
CloseHandle(stm->input_available_event);
CloseHandle(stm->thread);
// The variables intialized in wasapi_stream_init,
// must be destroyed in wasapi_stream_release.
stm->linear_input_buffer.reset();
{
auto_lock lock(stm->stream_reset_lock);
close_wasapi_stream(stm);
}
delete stm;
}
return result;
}
void
wasapi_stream_destroy(cubeb_stream * stm)
{
XASSERT(stm);
LOG("Stream destroy (%p)", stm);
LOG("Stream destroy called, decrementing ref count (%p)", stm);
if (!stop_and_join_render_thread(stm, OnDestroy)) {
// Emergency bailout: render thread becomes responsible for calling
// wasapi_stream_destroy.
return;
}
if (stm->notification_client) {
unregister_notification_client(stm);
}
{
auto_lock lock(stm->stream_reset_lock);
close_wasapi_stream(stm);
}
CloseHandle(stm->reconfigure_event);
CloseHandle(stm->refill_event);
CloseHandle(stm->input_available_event);
delete stm;
stop_and_join_render_thread(stm);
wasapi_stream_release(stm);
}
enum StreamDirection { OUTPUT, INPUT };
@ -2866,6 +2933,7 @@ enum StreamDirection { OUTPUT, INPUT };
int
stream_start_one_side(cubeb_stream * stm, StreamDirection dir)
{
XASSERT(stm);
XASSERT((dir == OUTPUT && stm->output_client) ||
(dir == INPUT && stm->input_client));
@ -2909,7 +2977,7 @@ wasapi_stream_start(cubeb_stream * stm)
{
auto_lock lock(stm->stream_reset_lock);
XASSERT(stm && !stm->thread && !stm->shutdown_event);
XASSERT(stm);
XASSERT(stm->output_client || stm->input_client);
if (stm->output_client) {
@ -2926,24 +2994,9 @@ wasapi_stream_start(cubeb_stream * stm)
}
}
stm->shutdown_event = CreateEvent(NULL, 0, 0, NULL);
if (!stm->shutdown_event) {
LOG("Can't create the shutdown event, error: %lx", GetLastError());
return CUBEB_ERROR;
}
stm->active = true;
cubeb_async_log_reset_threads();
stm->thread =
(HANDLE)_beginthreadex(NULL, 512 * 1024, wasapi_stream_render_loop, stm,
STACK_SIZE_PARAM_IS_A_RESERVATION, NULL);
if (stm->thread == NULL) {
LOG("could not create WASAPI render thread.");
CloseHandle(stm->shutdown_event);
stm->shutdown_event = 0;
return CUBEB_ERROR;
}
wasapi_state_callback(stm, stm->user_ptr, CUBEB_STATE_STARTED);
stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STARTED);
return CUBEB_OK;
}
@ -2973,13 +3026,9 @@ wasapi_stream_stop(cubeb_stream * stm)
}
}
wasapi_state_callback(stm, stm->user_ptr, CUBEB_STATE_STOPPED);
}
stm->active = false;
if (!stop_and_join_render_thread(stm, OnStop)) {
// If we could not join the thread, put the stream in error.
wasapi_state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR);
return CUBEB_ERROR;
wasapi_state_callback(stm, stm->user_ptr, CUBEB_STATE_STOPPED);
}
return CUBEB_OK;
@ -3114,8 +3163,9 @@ wstr_to_utf8(LPCWSTR str)
return ret;
}
static std::unique_ptr<wchar_t const []>
utf8_to_wstr(char const * str) {
static std::unique_ptr<wchar_t const[]>
utf8_to_wstr(char const * str)
{
int size = ::MultiByteToWideChar(CP_UTF8, 0, str, -1, nullptr, 0);
if (size <= 0) {
return nullptr;
@ -3126,8 +3176,8 @@ utf8_to_wstr(char const * str) {
return ret;
}
static com_ptr<IMMDevice> wasapi_get_device_node(
IMMDeviceEnumerator * enumerator, IMMDevice * dev)
static com_ptr<IMMDevice>
wasapi_get_device_node(IMMDeviceEnumerator * enumerator, IMMDevice * dev)
{
com_ptr<IMMDevice> ret;
com_ptr<IDeviceTopology> devtopo;

View file

@ -105,10 +105,13 @@ struct cubeb_stream {
int free_buffers;
int shutdown;
int draining;
int error;
HANDLE event;
HWAVEOUT waveout;
CRITICAL_SECTION lock;
uint64_t written;
/* number of frames written during preroll */
uint64_t position_base;
float soft_volume;
/* For position wrap-around handling: */
size_t frame_size;
@ -150,6 +153,14 @@ winmm_get_next_buffer(cubeb_stream * stm)
return hdr;
}
static long
preroll_callback(cubeb_stream * stream, void * user, const void * inputbuffer,
void * outputbuffer, long nframes)
{
memset((uint8_t *)outputbuffer, 0, nframes * bytes_per_frame(stream->params));
return nframes;
}
static void
winmm_refill_stream(cubeb_stream * stm)
{
@ -158,13 +169,20 @@ winmm_refill_stream(cubeb_stream * stm)
long wanted;
MMRESULT r;
ALOG("winmm_refill_stream");
EnterCriticalSection(&stm->lock);
if (stm->error) {
LeaveCriticalSection(&stm->lock);
return;
}
stm->free_buffers += 1;
XASSERT(stm->free_buffers > 0 && stm->free_buffers <= NBUFS);
if (stm->draining) {
LeaveCriticalSection(&stm->lock);
if (stm->free_buffers == NBUFS) {
ALOG("winmm_refill_stream draining");
stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_DRAINED);
}
SetEvent(stm->event);
@ -187,9 +205,10 @@ winmm_refill_stream(cubeb_stream * stm)
got = stm->data_callback(stm, stm->user_ptr, NULL, hdr->lpData, wanted);
EnterCriticalSection(&stm->lock);
if (got < 0) {
stm->error = 1;
LeaveCriticalSection(&stm->lock);
/* XXX handle this case */
XASSERT(0);
SetEvent(stm->event);
stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR);
return;
} else if (got < wanted) {
stm->draining = 1;
@ -224,6 +243,8 @@ winmm_refill_stream(cubeb_stream * stm)
return;
}
ALOG("winmm_refill_stream %ld frames", got);
LeaveCriticalSection(&stm->lock);
}
@ -486,7 +507,11 @@ winmm_stream_init(cubeb * context, cubeb_stream ** stream,
stm->params = *output_stream_params;
stm->data_callback = data_callback;
// Data callback is set to the user-provided data callback after
// the initialization and potential preroll callback calls are done, because
// cubeb users don't expect the data callback to be called during
// initialization.
stm->data_callback = preroll_callback;
stm->state_callback = state_callback;
stm->user_ptr = user_ptr;
stm->written = 0;
@ -553,9 +578,18 @@ winmm_stream_init(cubeb * context, cubeb_stream ** stream,
stm->frame_size = bytes_per_frame(stm->params);
stm->prev_pos_lo_dword = 0;
stm->pos_hi_dword = 0;
// Set the user data callback now that preroll has finished.
stm->data_callback = data_callback;
stm->position_base = 0;
// Offset the position by the number of frames written during preroll.
stm->position_base = stm->written;
stm->written = 0;
*stream = stm;
LOG("winmm_stream_init OK");
return CUBEB_OK;
}
@ -585,7 +619,7 @@ winmm_stream_destroy(cubeb_stream * stm)
LeaveCriticalSection(&stm->lock);
/* Wait for all blocks to complete. */
while (device_valid && enqueued > 0) {
while (device_valid && enqueued > 0 && !stm->error) {
DWORD rv = WaitForSingleObject(stm->event, INFINITE);
XASSERT(rv == WAIT_OBJECT_0);
@ -774,7 +808,17 @@ winmm_stream_get_position(cubeb_stream * stm, uint64_t * position)
return CUBEB_ERROR;
}
*position = update_64bit_position(stm, time.u.cb) / stm->frame_size;
uint64_t position_not_adjusted =
update_64bit_position(stm, time.u.cb) / stm->frame_size;
// Subtract the number of frames that were written while prerolling, during
// initialization.
if (position_not_adjusted < stm->position_base) {
*position = 0;
} else {
*position = position_not_adjusted - stm->position_base;
}
LeaveCriticalSection(&stm->lock);
return CUBEB_OK;
@ -787,17 +831,12 @@ winmm_stream_get_latency(cubeb_stream * stm, uint32_t * latency)
MMTIME time;
uint64_t written, position;
EnterCriticalSection(&stm->lock);
/* See the long comment above for why not just use TIME_SAMPLES here. */
time.wType = TIME_BYTES;
r = waveOutGetPosition(stm->waveout, &time, sizeof(time));
if (r != MMSYSERR_NOERROR || time.wType != TIME_BYTES) {
LeaveCriticalSection(&stm->lock);
return CUBEB_ERROR;
int rv = winmm_stream_get_position(stm, &position);
if (rv != CUBEB_OK) {
return rv;
}
position = update_64bit_position(stm, time.u.cb);
EnterCriticalSection(&stm->lock);
written = stm->written;
LeaveCriticalSection(&stm->lock);

View file

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