diff --git a/Externals/cubeb/CMakeLists.txt b/Externals/cubeb/CMakeLists.txt index e92712635a..3604041d7d 100644 --- a/Externals/cubeb/CMakeLists.txt +++ b/Externals/cubeb/CMakeLists.txt @@ -43,9 +43,11 @@ add_library(cubeb src/cubeb_resampler.cpp src/cubeb_panner.cpp src/cubeb_log.cpp - src/cubeb_utils.c + src/cubeb_strings.c $) -target_include_directories(cubeb PUBLIC include) +target_include_directories(cubeb + PUBLIC $ $ +) target_include_directories(cubeb PRIVATE src) target_compile_definitions(cubeb PRIVATE OUTSIDE_SPEEX) target_compile_definitions(cubeb PRIVATE FLOATING_POINT) @@ -56,7 +58,42 @@ add_sanitizers(cubeb) include(GenerateExportHeader) generate_export_header(cubeb EXPORT_FILE_NAME ${CMAKE_BINARY_DIR}/exports/cubeb_export.h) -target_include_directories(cubeb PUBLIC ${CMAKE_BINARY_DIR}/exports) +target_include_directories(cubeb + PUBLIC $ +) + +install(DIRECTORY ${CMAKE_SOURCE_DIR}/include DESTINATION ${CMAKE_INSTALL_PREFIX}) +install(DIRECTORY ${CMAKE_BINARY_DIR}/exports/ DESTINATION ${CMAKE_INSTALL_PREFIX}/include/cubeb) + +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 "lib/cmake/${PROJECT_NAME}" +) + +install(TARGETS cubeb + EXPORT "${PROJECT_NAME}Targets" + DESTINATION ${CMAKE_INSTALL_PREFIX} + LIBRARY DESTINATION "lib" + ARCHIVE DESTINATION "lib" + RUNTIME DESTINATION "bin" + INCLUDES DESTINATION "include" +) +install( + FILES "${PROJECT_BINARY_DIR}/${PROJECT_NAME}Config.cmake" "${PROJECT_BINARY_DIR}/${PROJECT_NAME}ConfigVersion.cmake" + DESTINATION "lib/cmake/${PROJECT_NAME}" +) +install( + EXPORT "${PROJECT_NAME}Targets" + NAMESPACE "${PROJECT_NAME}::" + DESTINATION "lib/cmake/${PROJECT_NAME}" +) add_library(speex OBJECT src/speex/resample.c) @@ -82,6 +119,7 @@ if(USE_PULSE) target_sources(cubeb PRIVATE src/cubeb_pulse.c) target_compile_definitions(cubeb PRIVATE USE_PULSE) + target_link_libraries(cubeb PRIVATE pulse) if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux") target_link_libraries(cubeb PRIVATE dl) endif() @@ -93,6 +131,9 @@ if(USE_ALSA) src/cubeb_alsa.c) target_compile_definitions(cubeb PRIVATE USE_ALSA) target_link_libraries(cubeb PRIVATE asound pthread) + if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux") + target_link_libraries(cubeb PRIVATE dl) + endif() endif() check_include_files(jack/jack.h USE_JACK) @@ -100,10 +141,10 @@ if(USE_JACK) target_sources(cubeb PRIVATE src/cubeb_jack.cpp) target_compile_definitions(cubeb PRIVATE USE_JACK) + target_link_libraries(cubeb PRIVATE jack pthread) if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux") target_link_libraries(cubeb PRIVATE dl) endif() - target_link_libraries(cubeb PRIVATE pthread) endif() check_include_files(audioclient.h USE_WASAPI) diff --git a/Externals/cubeb/Config.cmake.in b/Externals/cubeb/Config.cmake.in new file mode 100644 index 0000000000..c5326ef33a --- /dev/null +++ b/Externals/cubeb/Config.cmake.in @@ -0,0 +1,4 @@ +@PACKAGE_INIT@ + +include("${CMAKE_CURRENT_LIST_DIR}/cubebTargets.cmake") +check_required_components(cubeb) \ No newline at end of file diff --git a/Externals/cubeb/include/cubeb/cubeb.h b/Externals/cubeb/include/cubeb/cubeb.h index 5e09e2d301..9b091e8fad 100644 --- a/Externals/cubeb/include/cubeb/cubeb.h +++ b/Externals/cubeb/include/cubeb/cubeb.h @@ -33,11 +33,11 @@ extern "C" { cubeb * app_ctx; cubeb_init(&app_ctx, "Example Application"); int rv; - int rate; - int latency_frames; + uint32_t rate; + uint32_t latency_frames; uint64_t ts; - rv = cubeb_get_min_latency(app_ctx, output_params, &latency_frames); + rv = cubeb_get_min_latency(app_ctx, &output_params, &latency_frames); if (rv != CUBEB_OK) { fprintf(stderr, "Could not get minimum latency"); return rv; @@ -144,28 +144,6 @@ typedef enum { #endif } cubeb_sample_format; -#if defined(__ANDROID__) -/** - * This maps to the underlying stream types on supported platforms, e.g. - * Android. - */ -typedef enum { - CUBEB_STREAM_TYPE_VOICE_CALL = 0, - CUBEB_STREAM_TYPE_SYSTEM = 1, - CUBEB_STREAM_TYPE_RING = 2, - CUBEB_STREAM_TYPE_MUSIC = 3, - CUBEB_STREAM_TYPE_ALARM = 4, - CUBEB_STREAM_TYPE_NOTIFICATION = 5, - CUBEB_STREAM_TYPE_BLUETOOTH_SCO = 6, - CUBEB_STREAM_TYPE_SYSTEM_ENFORCED = 7, - CUBEB_STREAM_TYPE_DTMF = 8, - CUBEB_STREAM_TYPE_TTS = 9, - CUBEB_STREAM_TYPE_FM = 10, - - CUBEB_STREAM_TYPE_MAX -} cubeb_stream_type; -#endif - /** An opaque handle used to refer a particular input or output device * across calls. */ typedef void const * cubeb_devid; @@ -239,12 +217,9 @@ typedef enum { typedef struct { cubeb_sample_format format; /**< Requested sample format. One of #cubeb_sample_format. */ - unsigned int rate; /**< Requested sample rate. Valid range is [1000, 192000]. */ - unsigned int channels; /**< Requested channel count. Valid range is [1, 8]. */ + uint32_t rate; /**< Requested sample rate. Valid range is [1000, 192000]. */ + uint32_t channels; /**< Requested channel count. Valid range is [1, 8]. */ cubeb_channel_layout layout; /**< Requested channel layout. This must be consistent with the provided channels. */ -#if defined(__ANDROID__) - cubeb_stream_type stream_type; /**< Used to map Android audio stream types */ -#endif } cubeb_stream_params; /** Audio device description */ @@ -348,13 +323,13 @@ typedef struct { cubeb_device_fmt format; /**< Sample format supported. */ cubeb_device_fmt default_format; /**< The default sample format for this device. */ - unsigned int max_channels; /**< Channels. */ - unsigned int default_rate; /**< Default/Preferred sample rate. */ - unsigned int max_rate; /**< Maximum sample rate supported. */ - unsigned int min_rate; /**< Minimum sample rate supported. */ + uint32_t max_channels; /**< Channels. */ + uint32_t default_rate; /**< Default/Preferred sample rate. */ + uint32_t max_rate; /**< Maximum sample rate supported. */ + uint32_t min_rate; /**< Minimum sample rate supported. */ - unsigned int latency_lo; /**< Lowest possible latency in frames. */ - unsigned int latency_hi; /**< Higest possible latency in frames. */ + uint32_t latency_lo; /**< Lowest possible latency in frames. */ + uint32_t latency_hi; /**< Higest possible latency in frames. */ } cubeb_device_info; /** Device collection. @@ -405,7 +380,7 @@ typedef void (* cubeb_device_changed_callback)(void * user_ptr); /** * User supplied callback called when the underlying device collection changed. * @param context A pointer to the cubeb context. - * @param user_ptr The pointer passed to cubeb_stream_init. */ + * @param user_ptr The pointer passed to cubeb_register_device_collection_changed. */ typedef void (* cubeb_device_collection_changed_callback)(cubeb * context, void * user_ptr); @@ -455,7 +430,7 @@ CUBEB_EXPORT int cubeb_get_max_channel_count(cubeb * context, uint32_t * max_cha @retval CUBEB_ERROR_INVALID_PARAMETER @retval CUBEB_ERROR_NOT_SUPPORTED */ CUBEB_EXPORT int cubeb_get_min_latency(cubeb * context, - cubeb_stream_params params, + cubeb_stream_params * params, uint32_t * latency_frames); /** Get the preferred sample rate for this backend: this is hardware and @@ -512,7 +487,7 @@ CUBEB_EXPORT int cubeb_stream_init(cubeb * context, cubeb_stream_params * input_stream_params, cubeb_devid output_device, cubeb_stream_params * output_stream_params, - unsigned int latency_frames, + uint32_t latency_frames, cubeb_data_callback data_callback, cubeb_state_callback state_callback, void * user_ptr); @@ -534,6 +509,14 @@ CUBEB_EXPORT int cubeb_stream_start(cubeb_stream * stream); @retval CUBEB_ERROR */ CUBEB_EXPORT int cubeb_stream_stop(cubeb_stream * stream); +/** Reset stream to the default device. + @param stream + @retval CUBEB_OK + @retval CUBEB_ERROR_INVALID_PARAMETER + @retval CUBEB_ERROR_NOT_SUPPORTED + @retval CUBEB_ERROR */ +CUBEB_EXPORT int cubeb_stream_reset_default_device(cubeb_stream * stream); + /** Get the current stream playback position. @param stream @param position Playback position in frames. diff --git a/Externals/cubeb/msvc/cubeb.vcxproj b/Externals/cubeb/msvc/cubeb.vcxproj index 59585b0862..1447c36f67 100644 --- a/Externals/cubeb/msvc/cubeb.vcxproj +++ b/Externals/cubeb/msvc/cubeb.vcxproj @@ -57,6 +57,7 @@ + @@ -74,7 +75,7 @@ - + diff --git a/Externals/cubeb/msvc/cubeb.vcxproj.filters b/Externals/cubeb/msvc/cubeb.vcxproj.filters index f92ba0c32b..a695c1c9ec 100644 --- a/Externals/cubeb/msvc/cubeb.vcxproj.filters +++ b/Externals/cubeb/msvc/cubeb.vcxproj.filters @@ -11,6 +11,7 @@ + @@ -45,7 +46,7 @@ - + @@ -57,4 +58,4 @@ {caf7c7d0-0918-4299-8423-b287285d6fd0} - \ No newline at end of file + diff --git a/Externals/cubeb/src/cubeb-internal.h b/Externals/cubeb/src/cubeb-internal.h index b95246987d..25bad3a1d0 100644 --- a/Externals/cubeb/src/cubeb-internal.h +++ b/Externals/cubeb/src/cubeb-internal.h @@ -69,6 +69,7 @@ struct cubeb_ops { void (* stream_destroy)(cubeb_stream * stream); int (* stream_start)(cubeb_stream * stream); int (* stream_stop)(cubeb_stream * stream); + int (* stream_reset_default_device)(cubeb_stream * stream); int (* stream_get_position)(cubeb_stream * stream, uint64_t * position); int (* stream_get_latency)(cubeb_stream * stream, uint32_t * latency); int (* stream_set_volume)(cubeb_stream * stream, float volumes); diff --git a/Externals/cubeb/src/cubeb.c b/Externals/cubeb/src/cubeb.c index 714f88f722..f78f4aff14 100644 --- a/Externals/cubeb/src/cubeb.c +++ b/Externals/cubeb/src/cubeb.c @@ -245,9 +245,9 @@ cubeb_get_max_channel_count(cubeb * context, uint32_t * max_channels) } int -cubeb_get_min_latency(cubeb * context, cubeb_stream_params params, uint32_t * latency_ms) +cubeb_get_min_latency(cubeb * context, cubeb_stream_params * params, uint32_t * latency_ms) { - if (!context || !latency_ms) { + if (!context || !params || !latency_ms) { return CUBEB_ERROR_INVALID_PARAMETER; } @@ -255,7 +255,7 @@ cubeb_get_min_latency(cubeb * context, cubeb_stream_params params, uint32_t * la return CUBEB_ERROR_NOT_SUPPORTED; } - return context->ops->get_min_latency(context, params, latency_ms); + return context->ops->get_min_latency(context, *params, latency_ms); } int @@ -368,6 +368,20 @@ cubeb_stream_stop(cubeb_stream * stream) return stream->context->ops->stream_stop(stream); } +int +cubeb_stream_reset_default_device(cubeb_stream * stream) +{ + if (!stream) { + return CUBEB_ERROR_INVALID_PARAMETER; + } + + if (!stream->context->ops->stream_reset_default_device) { + return CUBEB_ERROR_NOT_SUPPORTED; + } + + return stream->context->ops->stream_reset_default_device(stream); +} + int cubeb_stream_get_position(cubeb_stream * stream, uint64_t * position) { @@ -574,13 +588,24 @@ int cubeb_enumerate_devices(cubeb * context, int cubeb_device_collection_destroy(cubeb * context, cubeb_device_collection * collection) { + int r; + if (context == NULL || collection == NULL) return CUBEB_ERROR_INVALID_PARAMETER; if (!context->ops->device_collection_destroy) return CUBEB_ERROR_NOT_SUPPORTED; - return context->ops->device_collection_destroy(context, collection); + if (!collection->device) + return CUBEB_OK; + + r = context->ops->device_collection_destroy(context, collection); + if (r == CUBEB_OK) { + collection->device = NULL; + collection->count = 0; + } + + return r; } int cubeb_register_device_collection_changed(cubeb * context, diff --git a/Externals/cubeb/src/cubeb_alsa.c b/Externals/cubeb/src/cubeb_alsa.c index 4bd80487dd..8cb37ee96c 100644 --- a/Externals/cubeb/src/cubeb_alsa.c +++ b/Externals/cubeb/src/cubeb_alsa.c @@ -17,7 +17,6 @@ #include #include "cubeb/cubeb.h" #include "cubeb-internal.h" -#include "cubeb_utils.h" #define CUBEB_STREAM_MAX 16 #define CUBEB_WATCHDOG_MS 10000 @@ -1361,6 +1360,7 @@ static struct cubeb_ops const alsa_ops = { .stream_destroy = alsa_stream_destroy, .stream_start = alsa_stream_start, .stream_stop = alsa_stream_stop, + .stream_reset_default_device = NULL, .stream_get_position = alsa_stream_get_position, .stream_get_latency = alsa_stream_get_latency, .stream_set_volume = alsa_stream_set_volume, diff --git a/Externals/cubeb/src/cubeb_audiotrack.c b/Externals/cubeb/src/cubeb_audiotrack.c index 0ffa9980e9..6b7f375cd4 100644 --- a/Externals/cubeb/src/cubeb_audiotrack.c +++ b/Externals/cubeb/src/cubeb_audiotrack.c @@ -145,9 +145,9 @@ audiotrack_get_min_frame_count(cubeb * ctx, cubeb_stream_params * params, int * status_t status; /* Recent Android have a getMinFrameCount method. */ if (!audiotrack_version_is_gingerbread(ctx)) { - status = ctx->klass.get_min_frame_count(min_frame_count, params->stream_type, params->rate); + 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, params->stream_type, params->rate); + 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"); @@ -325,7 +325,7 @@ audiotrack_stream_init(cubeb * ctx, cubeb_stream ** stream, char const * stream_ channels = stm->params.channels == 2 ? AUDIO_CHANNEL_OUT_STEREO_ICS : AUDIO_CHANNEL_OUT_MONO_ICS; } - ctx->klass.ctor(stm->instance, stm->params.stream_type, stm->params.rate, + 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); @@ -429,6 +429,7 @@ static struct cubeb_ops const audiotrack_ops = { .stream_destroy = audiotrack_stream_destroy, .stream_start = audiotrack_stream_start, .stream_stop = audiotrack_stream_stop, + .stream_reset_default_device = NULL, .stream_get_position = audiotrack_stream_get_position, .stream_get_latency = audiotrack_stream_get_latency, .stream_set_volume = audiotrack_stream_set_volume, diff --git a/Externals/cubeb/src/cubeb_audiounit.cpp b/Externals/cubeb/src/cubeb_audiounit.cpp index 3ee13553f2..cfde47dd74 100644 --- a/Externals/cubeb/src/cubeb_audiounit.cpp +++ b/Externals/cubeb/src/cubeb_audiounit.cpp @@ -63,6 +63,8 @@ void audiounit_stream_stop_internal(cubeb_stream * stm); void audiounit_stream_start_internal(cubeb_stream * stm); static void audiounit_close_stream(cubeb_stream *stm); static int audiounit_setup_stream(cubeb_stream *stm); +static std::vector +audiounit_get_devices_of_type(cubeb_device_type devtype); extern cubeb_ops const audiounit_ops; @@ -79,7 +81,7 @@ struct cubeb { // The queue is asynchronously deallocated once all references to it are released dispatch_queue_t serial_queue = dispatch_queue_create(DISPATCH_QUEUE_LABEL, DISPATCH_QUEUE_SERIAL); // Current used channel layout - cubeb_channel_layout layout; + std::atomic layout{ CUBEB_LAYOUT_UNDEFINED }; }; static std::unique_ptr @@ -107,6 +109,21 @@ to_string(io_side side) } } +typedef uint32_t device_flags_value; + +enum device_flags { + DEV_UKNOWN = 0x00, /* Unkown */ + DEV_INPUT = 0x01, /* Record device like mic */ + DEV_OUTPUT = 0x02, /* Playback device like speakers */ + DEV_SYSTEM_DEFAULT = 0x04, /* System default device */ + DEV_SELECTED_DEFAULT = 0x08, /* User selected to use the system default device */ +}; + +struct device_info { + AudioDeviceID id = kAudioObjectUnknown; + device_flags_value flags = DEV_UKNOWN; +}; + struct cubeb_stream { explicit cubeb_stream(cubeb * context); @@ -114,12 +131,12 @@ struct cubeb_stream { cubeb_data_callback data_callback = nullptr; cubeb_state_callback state_callback = nullptr; cubeb_device_changed_callback device_changed_callback = nullptr; + owned_critical_section device_changed_callback_lock; /* Stream creation parameters */ cubeb_stream_params input_stream_params = { CUBEB_SAMPLE_FLOAT32NE, 0, 0, CUBEB_LAYOUT_UNDEFINED }; cubeb_stream_params output_stream_params = { CUBEB_SAMPLE_FLOAT32NE, 0, 0, CUBEB_LAYOUT_UNDEFINED }; - bool is_default_input; - AudioDeviceID input_device = 0; - AudioDeviceID output_device = 0; + device_info input_device; + device_info output_device; /* User pointer of data_callback */ void * user_ptr = nullptr; /* Format descriptions */ @@ -524,8 +541,10 @@ audiounit_output_callback(void * user_ptr, } /* Mixing */ - unsigned long output_buffer_length = outBufferList->mBuffers[0].mDataByteSize; - audiounit_mix_output_buffer(stm, output_frames, output_buffer, output_buffer_length); + if (stm->output_stream_params.layout != CUBEB_LAYOUT_UNDEFINED) { + unsigned long output_buffer_length = outBufferList->mBuffers[0].mDataByteSize; + audiounit_mix_output_buffer(stm, output_frames, output_buffer, output_buffer_length); + } return noErr; } @@ -551,86 +570,95 @@ audiounit_get_backend_id(cubeb * /* ctx */) } #if !TARGET_OS_IPHONE -static int -audiounit_get_output_device_id(AudioDeviceID * device_id) -{ - UInt32 size; - OSStatus r; - AudioObjectPropertyAddress output_device_address = { - kAudioHardwarePropertyDefaultOutputDevice, - kAudioObjectPropertyScopeGlobal, - kAudioObjectPropertyElementMaster - }; - - size = sizeof(*device_id); - - r = AudioObjectGetPropertyData(kAudioObjectSystemObject, - &output_device_address, - 0, - NULL, - &size, - device_id); - if (r != noErr) { - LOG("output_device_id rv=%d", r); - return CUBEB_ERROR; - } - - return CUBEB_OK; -} - -static int -audiounit_get_input_device_id(AudioDeviceID * device_id) -{ - UInt32 size; - OSStatus r; - AudioObjectPropertyAddress input_device_address = { - kAudioHardwarePropertyDefaultInputDevice, - kAudioObjectPropertyScopeGlobal, - kAudioObjectPropertyElementMaster - }; - - size = sizeof(*device_id); - - r = AudioObjectGetPropertyData(kAudioObjectSystemObject, - &input_device_address, - 0, - NULL, - &size, - device_id); - if (r != noErr) { - return CUBEB_ERROR; - } - - return CUBEB_OK; -} static int audiounit_stream_get_volume(cubeb_stream * stm, float * volume); static int audiounit_stream_set_volume(cubeb_stream * stm, float volume); static int audiounit_uninstall_device_changed_callback(cubeb_stream * stm); +static AudioObjectID audiounit_get_default_device_id(cubeb_device_type type); static int -audiounit_reinit_stream(cubeb_stream * stm) +audiounit_set_device_info(cubeb_stream * stm, AudioDeviceID id, io_side side) +{ + assert(stm); + + device_info * info = nullptr; + cubeb_device_type type = CUBEB_DEVICE_TYPE_UNKNOWN; + + if (side == INPUT) { + info = &stm->input_device; + type = CUBEB_DEVICE_TYPE_INPUT; + } else if (side == OUTPUT) { + info = &stm->output_device; + type = CUBEB_DEVICE_TYPE_OUTPUT; + } + memset(info, 0, sizeof(device_info)); + info->id = id; + + if (side == INPUT) { + info->flags |= DEV_INPUT; + } else if (side == OUTPUT) { + info->flags |= DEV_OUTPUT; + } + + AudioDeviceID default_device_id = audiounit_get_default_device_id(type); + if (default_device_id == kAudioObjectUnknown) { + return CUBEB_ERROR; + } + if (id == kAudioObjectUnknown) { + info->id = default_device_id; + info->flags |= DEV_SELECTED_DEFAULT; + } + + if (info->id == default_device_id) { + info->flags |= DEV_SYSTEM_DEFAULT; + } + + assert(info->id); + assert(info->flags & DEV_INPUT && !(info->flags & DEV_OUTPUT) || + !(info->flags & DEV_INPUT) && info->flags & DEV_OUTPUT); + + return CUBEB_OK; +} + + +static int +audiounit_reinit_stream(cubeb_stream * stm, device_flags_value flags) { auto_lock context_lock(stm->context->mutex); + assert((flags & DEV_INPUT && stm->input_unit) || + (flags & DEV_OUTPUT && stm->output_unit)); if (!stm->shutdown) { audiounit_stream_stop_internal(stm); } int r = audiounit_uninstall_device_changed_callback(stm); if (r != CUBEB_OK) { - LOG("(%p) Could not uninstall the device changed callback", stm); + LOG("(%p) Could not uninstall all device change listeners.", stm); } { auto_lock lock(stm->mutex); float volume = 0.0; int vol_rv = CUBEB_ERROR; - if (has_output(stm)) { + if (stm->output_unit) { vol_rv = audiounit_stream_get_volume(stm, &volume); } audiounit_close_stream(stm); + /* Reinit occurs in 2 cases. When the device is not alive any more and when the + * default system device change. In both cases cubeb switch on the new default + * device. This is considered the most expected behavior for the user. */ + if (flags & DEV_INPUT) { + r = audiounit_set_device_info(stm, 0, INPUT); + assert(r == CUBEB_OK); + } + /* Always use the default output on reinit. This is not correct in every case + * but it is sufficient for Firefox and prevent reinit from reporting failures. + * It will change soon when reinit mechanism will be updated. */ + r = audiounit_set_device_info(stm, 0, OUTPUT); + assert(r == CUBEB_OK); + if (audiounit_setup_stream(stm) != CUBEB_OK) { LOG("(%p) Stream reinit failed.", stm); return CUBEB_ERROR; @@ -652,47 +680,77 @@ audiounit_reinit_stream(cubeb_stream * stm) return CUBEB_OK; } +static char const * +event_addr_to_string(AudioObjectPropertySelector selector) +{ + switch(selector) { + case kAudioHardwarePropertyDefaultOutputDevice: + return "kAudioHardwarePropertyDefaultOutputDevice"; + case kAudioHardwarePropertyDefaultInputDevice: + return "kAudioHardwarePropertyDefaultInputDevice"; + case kAudioDevicePropertyDeviceIsAlive: + return "kAudioDevicePropertyDeviceIsAlive"; + case kAudioDevicePropertyDataSource: + return "kAudioDevicePropertyDataSource"; + default: + return "Unknown"; + } +} + static OSStatus -audiounit_property_listener_callback(AudioObjectID /* id */, UInt32 address_count, +audiounit_property_listener_callback(AudioObjectID id, UInt32 address_count, const AudioObjectPropertyAddress * addresses, void * user) { cubeb_stream * stm = (cubeb_stream*) user; + if (stm->switching_device) { + LOG("Switching is already taking place. Skip Event %s for id=%d", event_addr_to_string(addresses[0].mSelector), id); + return noErr; + } stm->switching_device = true; + device_flags_value switch_side = DEV_UKNOWN; LOG("(%p) Audio device changed, %u events.", stm, (unsigned int) address_count); for (UInt32 i = 0; i < address_count; i++) { switch(addresses[i].mSelector) { case kAudioHardwarePropertyDefaultOutputDevice: { - LOG("Event[%u] - mSelector == kAudioHardwarePropertyDefaultOutputDevice", (unsigned int) i); + LOG("Event[%u] - mSelector == kAudioHardwarePropertyDefaultOutputDevice for id=%d", (unsigned int) i, id); // Allow restart to choose the new default - stm->output_device = 0; + switch_side |= DEV_OUTPUT; } break; case kAudioHardwarePropertyDefaultInputDevice: { - LOG("Event[%u] - mSelector == kAudioHardwarePropertyDefaultInputDevice", (unsigned int) i); + LOG("Event[%u] - mSelector == kAudioHardwarePropertyDefaultInputDevice for id=%d", (unsigned int) i, id); // Allow restart to choose the new default - stm->input_device = 0; + switch_side |= DEV_INPUT; } break; case kAudioDevicePropertyDeviceIsAlive: { - LOG("Event[%u] - mSelector == kAudioDevicePropertyDeviceIsAlive", (unsigned int) i); + LOG("Event[%u] - mSelector == kAudioDevicePropertyDeviceIsAlive for id=%d", (unsigned int) i, id); // If this is the default input device ignore the event, // kAudioHardwarePropertyDefaultInputDevice will take care of the switch - if (stm->is_default_input) { + if (stm->input_device.flags & DEV_SYSTEM_DEFAULT) { LOG("It's the default input device, ignore the event"); + stm->switching_device = false; return noErr; } // Allow restart to choose the new default. Event register only for input. - stm->input_device = 0; + switch_side |= DEV_INPUT; } break; case kAudioDevicePropertyDataSource: { - LOG("Event[%u] - mSelector == kAudioHardwarePropertyDataSource", (unsigned int) i); - return noErr; + LOG("Event[%u] - mSelector == kAudioHardwarePropertyDataSource for id=%d", (unsigned int) i, id); + if (stm->input_unit) { + switch_side |= DEV_INPUT; + } + if (stm->output_unit) { + switch_side |= DEV_OUTPUT; + } } + break; default: LOG("Event[%u] - mSelector == Unexpected Event id %d, return", (unsigned int) i, addresses[i].mSelector); + stm->switching_device = false; return noErr; } } @@ -704,7 +762,7 @@ audiounit_property_listener_callback(AudioObjectID /* id */, UInt32 address_coun case kAudioDevicePropertyDeviceIsAlive: /* fall through */ case kAudioDevicePropertyDataSource: { - auto_lock lock(stm->mutex); + auto_lock dev_cb_lock(stm->device_changed_callback_lock); if (stm->device_changed_callback) { stm->device_changed_callback(stm->user_ptr); } @@ -716,7 +774,7 @@ audiounit_property_listener_callback(AudioObjectID /* id */, UInt32 address_coun // Use a new thread, through the queue, to avoid deadlock when calling // Get/SetProperties method from inside notify callback dispatch_async(stm->context->serial_queue, ^() { - if (audiounit_reinit_stream(stm) != CUBEB_OK) { + if (audiounit_reinit_stream(stm, switch_side) != CUBEB_OK) { stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STOPPED); LOG("(%p) Could not reopen the stream after switching.", stm); } @@ -754,66 +812,43 @@ audiounit_remove_listener(cubeb_stream * stm, AudioDeviceID id, return AudioObjectRemovePropertyListener(id, &address, listener, stm); } -static AudioObjectID audiounit_get_default_device_id(cubeb_device_type type); - -static AudioObjectID -audiounit_get_input_device_id(cubeb_stream * stm) -{ - AudioObjectID input_dev = stm->input_device ? stm->input_device : - audiounit_get_default_device_id(CUBEB_DEVICE_TYPE_INPUT); - assert(input_dev); - return input_dev; -} - static int audiounit_install_device_changed_callback(cubeb_stream * stm) { - OSStatus r; + OSStatus rv; + int r = CUBEB_OK; if (stm->output_unit) { /* This event will notify us when the data source on the same device changes, * for example when the user plugs in a normal (non-usb) headset in the * headphone jack. */ - AudioDeviceID output_dev_id; - r = audiounit_get_output_device_id(&output_dev_id); - if (r != noErr) { - return CUBEB_ERROR; - } - - r = audiounit_add_listener(stm, output_dev_id, kAudioDevicePropertyDataSource, + rv = audiounit_add_listener(stm, stm->output_device.id, kAudioDevicePropertyDataSource, kAudioDevicePropertyScopeOutput, &audiounit_property_listener_callback); - if (r != noErr) { - LOG("AudioObjectAddPropertyListener/output/kAudioDevicePropertyDataSource rv=%d", r); - return CUBEB_ERROR; + if (rv != noErr) { + LOG("AudioObjectAddPropertyListener/output/kAudioDevicePropertyDataSource rv=%d, device id=%d", rv, stm->output_device.id); + r = CUBEB_ERROR; } } if (stm->input_unit) { /* This event will notify us when the data source on the input device changes. */ - AudioDeviceID input_dev_id; - r = audiounit_get_input_device_id(&input_dev_id); - if (r != noErr) { - return CUBEB_ERROR; - } - - r = audiounit_add_listener(stm, input_dev_id, kAudioDevicePropertyDataSource, + rv = audiounit_add_listener(stm, stm->input_device.id, kAudioDevicePropertyDataSource, kAudioDevicePropertyScopeInput, &audiounit_property_listener_callback); - if (r != noErr) { - LOG("AudioObjectAddPropertyListener/input/kAudioDevicePropertyDataSource rv=%d", r); - return CUBEB_ERROR; + if (rv != noErr) { + LOG("AudioObjectAddPropertyListener/input/kAudioDevicePropertyDataSource rv=%d, device id=%d", rv, stm->input_device.id); + r = CUBEB_ERROR; } /* Event to notify when the input is going away. */ - AudioDeviceID dev = audiounit_get_input_device_id(stm); - r = audiounit_add_listener(stm, dev, kAudioDevicePropertyDeviceIsAlive, + rv = audiounit_add_listener(stm, stm->input_device.id, kAudioDevicePropertyDeviceIsAlive, kAudioObjectPropertyScopeGlobal, &audiounit_property_listener_callback); - if (r != noErr) { - LOG("AudioObjectAddPropertyListener/input/kAudioDevicePropertyDeviceIsAlive rv=%d", r); - return CUBEB_ERROR; + if (rv != noErr) { + LOG("AudioObjectAddPropertyListener/input/kAudioDevicePropertyDeviceIsAlive rv=%d, device id =%d", rv, stm->input_device.id); + r = CUBEB_ERROR; } } - return CUBEB_OK; + return r; } static int @@ -850,44 +885,35 @@ audiounit_install_system_changed_callback(cubeb_stream * stm) static int audiounit_uninstall_device_changed_callback(cubeb_stream * stm) { - OSStatus r; + OSStatus rv; + // Failing to uninstall listeners is not a fatal error. + int r = CUBEB_OK; if (stm->output_unit) { - AudioDeviceID output_dev_id; - r = audiounit_get_output_device_id(&output_dev_id); - if (r != noErr) { - return CUBEB_ERROR; - } - - r = audiounit_remove_listener(stm, output_dev_id, kAudioDevicePropertyDataSource, + rv = audiounit_remove_listener(stm, stm->output_device.id, kAudioDevicePropertyDataSource, kAudioDevicePropertyScopeOutput, &audiounit_property_listener_callback); - if (r != noErr) { - return CUBEB_ERROR; + if (rv != noErr) { + LOG("AudioObjectRemovePropertyListener/output/kAudioDevicePropertyDataSource rv=%d, device id=%d", rv, stm->output_device.id); + r = CUBEB_ERROR; } } if (stm->input_unit) { - AudioDeviceID input_dev_id; - r = audiounit_get_input_device_id(&input_dev_id); - if (r != noErr) { - return CUBEB_ERROR; - } - - r = audiounit_remove_listener(stm, input_dev_id, kAudioDevicePropertyDataSource, + rv = audiounit_remove_listener(stm, stm->input_device.id, kAudioDevicePropertyDataSource, kAudioDevicePropertyScopeInput, &audiounit_property_listener_callback); - if (r != noErr) { - return CUBEB_ERROR; + if (rv != noErr) { + LOG("AudioObjectRemovePropertyListener/input/kAudioDevicePropertyDataSource rv=%d, device id=%d", rv, stm->input_device.id); + r = CUBEB_ERROR; } - AudioDeviceID dev = audiounit_get_input_device_id(stm); - r = audiounit_remove_listener(stm, dev, kAudioDevicePropertyDeviceIsAlive, + rv = audiounit_remove_listener(stm, stm->input_device.id, kAudioDevicePropertyDeviceIsAlive, kAudioObjectPropertyScopeGlobal, &audiounit_property_listener_callback); - if (r != noErr) { - LOG("AudioObjectRemovePropertyListener/input/kAudioDevicePropertyDeviceIsAlive rv=%d", r); - return CUBEB_ERROR; + if (rv != noErr) { + LOG("AudioObjectRemovePropertyListener/input/kAudioDevicePropertyDeviceIsAlive rv=%d, device id=%d", rv, stm->input_device.id); + r = CUBEB_ERROR; } } - return CUBEB_OK; + return r; } static int @@ -926,7 +952,8 @@ audiounit_get_acceptable_latency_range(AudioValueRange * latency_range) kAudioObjectPropertyElementMaster }; - if (audiounit_get_output_device_id(&output_device_id) != CUBEB_OK) { + output_device_id = audiounit_get_default_device_id(CUBEB_DEVICE_TYPE_OUTPUT); + if (output_device_id == kAudioObjectUnknown) { LOG("Could not get default output device id."); return CUBEB_ERROR; } @@ -991,7 +1018,8 @@ audiounit_get_max_channel_count(cubeb * ctx, uint32_t * max_channels) assert(ctx && max_channels); - if (audiounit_get_output_device_id(&output_device_id) != CUBEB_OK) { + output_device_id = audiounit_get_default_device_id(CUBEB_DEVICE_TYPE_OUTPUT); + if (output_device_id == kAudioObjectUnknown) { return CUBEB_ERROR; } @@ -1052,7 +1080,8 @@ audiounit_get_preferred_sample_rate(cubeb * /* ctx */, uint32_t * rate) kAudioObjectPropertyElementMaster }; - if (audiounit_get_output_device_id(&output_device_id) != CUBEB_OK) { + output_device_id = audiounit_get_default_device_id(CUBEB_DEVICE_TYPE_OUTPUT); + if (output_device_id == kAudioObjectUnknown) { return CUBEB_ERROR; } @@ -1139,7 +1168,8 @@ audiounit_get_preferred_channel_layout() UInt32 size = 0; AudioDeviceID id; - if (audiounit_get_output_device_id(&id) != CUBEB_OK) { + id = audiounit_get_default_device_id(CUBEB_DEVICE_TYPE_OUTPUT); + if (id == kAudioObjectUnknown) { return CUBEB_LAYOUT_UNDEFINED; } @@ -1161,7 +1191,7 @@ audiounit_get_preferred_channel_layout() return audiounit_convert_channel_layout(layout.get()); } -static int audiounit_create_unit(AudioUnit * unit, io_side side, AudioDeviceID device); +static int audiounit_create_unit(AudioUnit * unit, device_info * device); static int audiounit_get_preferred_channel_layout(cubeb * ctx, cubeb_channel_layout * layout) @@ -1185,8 +1215,13 @@ audiounit_get_preferred_channel_layout(cubeb * ctx, cubeb_channel_layout * layou // If there is no existed stream, then we create a default ouput unit and // use it to get the current used channel layout. AudioUnit output_unit = nullptr; - audiounit_create_unit(&output_unit, OUTPUT, 0); - *layout = audiounit_get_current_channel_layout(output_unit); + device_info default_out_device; + default_out_device.id = audiounit_get_default_device_id(CUBEB_DEVICE_TYPE_OUTPUT); + default_out_device.flags = (DEV_OUTPUT | DEV_SYSTEM_DEFAULT); + if (default_out_device.id != kAudioObjectUnknown) { + audiounit_create_unit(&output_unit, &default_out_device); + *layout = audiounit_get_current_channel_layout(output_unit); + } } if (*layout == CUBEB_LAYOUT_UNDEFINED) { @@ -1462,11 +1497,16 @@ audiounit_create_blank_aggregate_device(AudioObjectID * plugin_id, AudioDeviceID CFDictionaryAddValue(aggregate_device_dict, CFSTR(kAudioAggregateDeviceUIDKey), aggregate_device_UID); CFRelease(aggregate_device_UID); - int private_key = 1; - CFNumberRef aggregate_device_private_key = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &private_key); + int private_value = 1; + CFNumberRef aggregate_device_private_key = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &private_value); CFDictionaryAddValue(aggregate_device_dict, CFSTR(kAudioAggregateDeviceIsPrivateKey), aggregate_device_private_key); CFRelease(aggregate_device_private_key); + int stacked_value = 0; + CFNumberRef aggregate_device_stacked_key = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &stacked_value); + CFDictionaryAddValue(aggregate_device_dict, CFSTR(kAudioAggregateDeviceIsStackedKey), aggregate_device_stacked_key); + CFRelease(aggregate_device_stacked_key); + r = AudioObjectGetPropertyData(*plugin_id, &create_aggregate_device_address, sizeof(aggregate_device_dict), @@ -1502,20 +1542,22 @@ audiounit_set_aggregate_sub_device_list(AudioDeviceID aggregate_device_id, { LOG("Add devices input %u and output %u into aggregate device %u", input_device_id, output_device_id, aggregate_device_id); - const std::vector input_sub_devices = audiounit_get_sub_devices(input_device_id); const std::vector output_sub_devices = audiounit_get_sub_devices(output_device_id); + const std::vector input_sub_devices = audiounit_get_sub_devices(input_device_id); CFMutableArrayRef aggregate_sub_devices_array = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks); - for (UInt32 i = 0; i < input_sub_devices.size(); i++) { - CFStringRef ref = get_device_name(input_sub_devices[i]); + /* The order of the items in the array is significant and is used to determine the order of the streams + of the AudioAggregateDevice. */ + for (UInt32 i = 0; i < output_sub_devices.size(); i++) { + CFStringRef ref = get_device_name(output_sub_devices[i]); if (ref == NULL) { CFRelease(aggregate_sub_devices_array); return CUBEB_ERROR; } CFArrayAppendValue(aggregate_sub_devices_array, ref); } - for (UInt32 i = 0; i < output_sub_devices.size(); i++) { - CFStringRef ref = get_device_name(output_sub_devices[i]); + for (UInt32 i = 0; i < input_sub_devices.size(); i++) { + CFStringRef ref = get_device_name(input_sub_devices[i]); if (ref == NULL) { CFRelease(aggregate_sub_devices_array); return CUBEB_ERROR; @@ -1612,7 +1654,8 @@ audiounit_activate_clock_drift_compensation(const AudioDeviceID aggregate_device kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster }; - for (UInt32 i = 0; i < subdevices_num; ++i) { + // Start from the second device since the first is the master clock + for (UInt32 i = 1; i < subdevices_num; ++i) { UInt32 drift_compensation_value = 1; rv = AudioObjectSetPropertyData(sub_devices[i], &address_drift, @@ -1628,7 +1671,7 @@ audiounit_activate_clock_drift_compensation(const AudioDeviceID aggregate_device return CUBEB_OK; } -static int audiounit_destroy_aggregate_device(AudioObjectID plugin_id, AudioDeviceID aggregate_device_id); +static int audiounit_destroy_aggregate_device(AudioObjectID plugin_id, AudioDeviceID * aggregate_device_id); /* * Aggregate Device is a virtual audio interface which utilizes inputs and outputs @@ -1655,30 +1698,28 @@ audiounit_create_aggregate_device(cubeb_stream * stm) int r = audiounit_create_blank_aggregate_device(&stm->plugin_id, &stm->aggregate_device_id); if (r != CUBEB_OK) { LOG("(%p) Failed to create blank aggregate device", stm); - audiounit_destroy_aggregate_device(stm->plugin_id, stm->aggregate_device_id); + audiounit_destroy_aggregate_device(stm->plugin_id, &stm->aggregate_device_id); return CUBEB_ERROR; } - AudioDeviceID input_device_id = audiounit_get_input_device_id(stm); - AudioDeviceID output_device_id = audiounit_get_default_device_id(CUBEB_DEVICE_TYPE_OUTPUT); - r = audiounit_set_aggregate_sub_device_list(stm->aggregate_device_id, input_device_id, output_device_id); + r = audiounit_set_aggregate_sub_device_list(stm->aggregate_device_id, stm->input_device.id, stm->output_device.id); if (r != CUBEB_OK) { LOG("(%p) Failed to set aggregate sub-device list", stm); - audiounit_destroy_aggregate_device(stm->plugin_id, stm->aggregate_device_id); + audiounit_destroy_aggregate_device(stm->plugin_id, &stm->aggregate_device_id); return CUBEB_ERROR; } r = audiounit_set_master_aggregate_device(stm->aggregate_device_id); if (r != CUBEB_OK) { LOG("(%p) Failed to set master sub-device for aggregate device", stm); - audiounit_destroy_aggregate_device(stm->plugin_id, stm->aggregate_device_id); + audiounit_destroy_aggregate_device(stm->plugin_id, &stm->aggregate_device_id); return CUBEB_ERROR; } r = audiounit_activate_clock_drift_compensation(stm->aggregate_device_id); if (r != CUBEB_OK) { LOG("(%p) Failed to activate clock drift compensation for aggregate device", stm); - audiounit_destroy_aggregate_device(stm->plugin_id, stm->aggregate_device_id); + audiounit_destroy_aggregate_device(stm->plugin_id, &stm->aggregate_device_id); return CUBEB_ERROR; } @@ -1686,8 +1727,11 @@ audiounit_create_aggregate_device(cubeb_stream * stm) } static int -audiounit_destroy_aggregate_device(AudioObjectID plugin_id, AudioDeviceID aggregate_device_id) +audiounit_destroy_aggregate_device(AudioObjectID plugin_id, AudioDeviceID * aggregate_device_id) { + assert(aggregate_device_id && + *aggregate_device_id != kAudioDeviceUnknown && + plugin_id != kAudioObjectUnknown); AudioObjectPropertyAddress destroy_aggregate_device_addr = { kAudioPlugInDestroyAggregateDevice, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster}; @@ -1707,17 +1751,19 @@ audiounit_destroy_aggregate_device(AudioObjectID plugin_id, AudioDeviceID aggreg 0, NULL, &size, - &aggregate_device_id); + aggregate_device_id); if (rv != noErr) { LOG("AudioObjectGetPropertyData/kAudioPlugInDestroyAggregateDevice, rv=%d", rv); return CUBEB_ERROR; } + LOG("Destroyed aggregate device %d", *aggregate_device_id); + *aggregate_device_id = 0; return CUBEB_OK; } static int -audiounit_new_unit_instance(AudioUnit * unit, io_side side, AudioDeviceID device) +audiounit_new_unit_instance(AudioUnit * unit, device_info * device) { AudioComponentDescription desc; AudioComponent comp; @@ -1725,15 +1771,14 @@ audiounit_new_unit_instance(AudioUnit * unit, io_side side, AudioDeviceID device desc.componentType = kAudioUnitType_Output; #if TARGET_OS_IPHONE - bool use_default_output = false; desc.componentSubType = kAudioUnitSubType_RemoteIO; #else // Use the DefaultOutputUnit for output when no device is specified // so we retain automatic output device switching when the default // changes. Once we have complete support for device notifications // and switching, we can use the AUHAL for everything. - bool use_default_output = device == 0 && (side == OUTPUT); - if (use_default_output) { + if ((device->flags & DEV_SYSTEM_DEFAULT) + && (device->flags & DEV_OUTPUT)) { desc.componentSubType = kAudioUnitSubType_DefaultOutput; } else { desc.componentSubType = kAudioUnitSubType_HALOutput; @@ -1779,71 +1824,60 @@ audiounit_enable_unit_scope(AudioUnit * unit, io_side side, enable_state state) } static int -audiounit_create_unit(AudioUnit * unit, io_side side, AudioDeviceID device) +audiounit_create_unit(AudioUnit * unit, device_info * device) { - AudioDeviceID devid; + assert(*unit == nullptr); + assert(device); + OSStatus rv; int r; - assert(*unit == nullptr); - r = audiounit_new_unit_instance(unit, side, device); + r = audiounit_new_unit_instance(unit, device); if (r != CUBEB_OK) { return r; } assert(*unit); -#if TARGET_OS_IPHONE - bool use_default_output = false; -#else - bool use_default_output = device == 0 && (side == OUTPUT); -#endif + if ((device->flags & DEV_SYSTEM_DEFAULT) + && (device->flags & DEV_OUTPUT)) { + return CUBEB_OK; + } - if (!use_default_output) { - switch (side) { - case INPUT: - r = audiounit_enable_unit_scope(unit, INPUT, ENABLE); - if (r != CUBEB_OK) { - LOG("Failed to enable audiounit input scope "); - return r; - } - r = audiounit_enable_unit_scope(unit, OUTPUT, DISABLE); - if (r != CUBEB_OK) { - LOG("Failed to disable audiounit output scope "); - return r; - } - break; - case OUTPUT: - r = audiounit_enable_unit_scope(unit, OUTPUT, ENABLE); - if (r != CUBEB_OK) { - LOG("Failed to enable audiounit output scope "); - return r; - } - r = audiounit_enable_unit_scope(unit, INPUT, DISABLE); - if (r != CUBEB_OK) { - LOG("Failed to disable audiounit input scope "); - return r; - } - break; - default: - assert(false); - } - if (device == 0) { - assert(side == INPUT); - devid = audiounit_get_default_device_id(CUBEB_DEVICE_TYPE_INPUT); - } else { - devid = device; + if (device->flags & DEV_INPUT) { + r = audiounit_enable_unit_scope(unit, INPUT, ENABLE); + if (r != CUBEB_OK) { + LOG("Failed to enable audiounit input scope "); + return r; } + r = audiounit_enable_unit_scope(unit, OUTPUT, DISABLE); + if (r != CUBEB_OK) { + LOG("Failed to disable audiounit output scope "); + return r; + } + } else if (device->flags & DEV_OUTPUT) { + r = audiounit_enable_unit_scope(unit, OUTPUT, ENABLE); + if (r != CUBEB_OK) { + LOG("Failed to enable audiounit output scope "); + return r; + } + r = audiounit_enable_unit_scope(unit, INPUT, DISABLE); + if (r != CUBEB_OK) { + LOG("Failed to disable audiounit input scope "); + return r; + } + } else { + assert(false); + } - rv = AudioUnitSetProperty(*unit, - kAudioOutputUnitProperty_CurrentDevice, - kAudioUnitScope_Global, - 0, - &devid, sizeof(AudioDeviceID)); - if (rv != noErr) { - LOG("AudioUnitSetProperty/kAudioOutputUnitProperty_CurrentDevice rv=%d", rv); - return CUBEB_ERROR; - } + rv = AudioUnitSetProperty(*unit, + kAudioOutputUnitProperty_CurrentDevice, + kAudioUnitScope_Global, + 0, + &device->id, sizeof(AudioDeviceID)); + if (rv != noErr) { + LOG("AudioUnitSetProperty/kAudioOutputUnitProperty_CurrentDevice rv=%d", rv); + return CUBEB_ERROR; } return CUBEB_OK; @@ -2252,9 +2286,10 @@ audiounit_configure_output(cubeb_stream * stm) return CUBEB_ERROR; } - audiounit_layout_init(stm, OUTPUT); - audiounit_init_mixer(stm); - + if (stm->output_stream_params.layout != CUBEB_LAYOUT_UNDEFINED) { + audiounit_layout_init(stm, OUTPUT); + audiounit_init_mixer(stm); + } LOG("(%p) Output audiounit init successfully.", stm); return CUBEB_OK; } @@ -2266,9 +2301,11 @@ audiounit_setup_stream(cubeb_stream * stm) int r = 0; - AudioDeviceID in_dev = stm->input_device; - AudioDeviceID out_dev = stm->output_device; - if (has_input(stm) && has_output(stm)) { + device_info in_dev_info = stm->input_device; + device_info out_dev_info = stm->output_device; + + if (has_input(stm) && has_output(stm) && + stm->input_device.id != stm->output_device.id) { r = audiounit_create_aggregate_device(stm); if (r != CUBEB_OK) { stm->aggregate_device_id = 0; @@ -2279,14 +2316,14 @@ audiounit_setup_stream(cubeb_stream * stm) // it after a couple of weeks. return r; } else { - in_dev = out_dev = stm->aggregate_device_id; + in_dev_info.id = out_dev_info.id = stm->aggregate_device_id; + in_dev_info.flags = DEV_INPUT; + out_dev_info.flags = DEV_OUTPUT; } } if (has_input(stm)) { - r = audiounit_create_unit(&stm->input_unit, - INPUT, - in_dev); + r = audiounit_create_unit(&stm->input_unit, &in_dev_info); if (r != CUBEB_OK) { LOG("(%p) AudioUnit creation for input failed.", stm); return r; @@ -2294,9 +2331,7 @@ audiounit_setup_stream(cubeb_stream * stm) } if (has_output(stm)) { - r = audiounit_create_unit(&stm->output_unit, - OUTPUT, - out_dev); + r = audiounit_create_unit(&stm->output_unit, &out_dev_info); if (r != CUBEB_OK) { LOG("(%p) AudioUnit creation for output failed.", stm); return r; @@ -2434,8 +2469,7 @@ audiounit_setup_stream(cubeb_stream * stm) r = audiounit_install_device_changed_callback(stm); if (r != CUBEB_OK) { - LOG("(%p) Could not install the device change callback.", stm); - return r; + LOG("(%p) Could not install all device change callback.", stm); } @@ -2464,7 +2498,9 @@ audiounit_stream_init(cubeb * context, cubeb_state_callback state_callback, void * user_ptr) { - std::unique_ptr stm(nullptr, audiounit_stream_destroy); + std::unique_ptr stm(new cubeb_stream(context), + audiounit_stream_destroy); + context->active_streams += 1; int r; assert(context); @@ -2475,8 +2511,6 @@ audiounit_stream_init(cubeb * context, return CUBEB_ERROR_INVALID_PARAMETER; } - stm.reset(new cubeb_stream(context)); - /* These could be different in the future if we have both * full-duplex stream and different devices for input vs output. */ stm->data_callback = data_callback; @@ -2485,13 +2519,19 @@ audiounit_stream_init(cubeb * context, stm->latency_frames = latency_frames; if (input_stream_params) { stm->input_stream_params = *input_stream_params; - stm->input_device = reinterpret_cast(input_device); - stm->is_default_input = stm->input_device == 0 || - (audiounit_get_default_device_id(CUBEB_DEVICE_TYPE_INPUT) == stm->input_device); + r = audiounit_set_device_info(stm.get(), reinterpret_cast(input_device), INPUT); + if (r != CUBEB_OK) { + LOG("(%p) Fail to set device info for input.", stm.get()); + return r; + } } if (output_stream_params) { stm->output_stream_params = *output_stream_params; - stm->output_device = reinterpret_cast(output_device); + r = audiounit_set_device_info(stm.get(), reinterpret_cast(output_device), OUTPUT); + if (r != CUBEB_OK) { + LOG("(%p) Fail to set device info for output.", stm.get()); + return r; + } } auto_lock context_lock(context->mutex); @@ -2499,7 +2539,6 @@ audiounit_stream_init(cubeb * context, // It's not critical to lock here, because no other thread has been started // yet, but it allows to assert that the lock has been taken in // `audiounit_setup_stream`. - context->active_streams += 1; auto_lock lock(stm->mutex); r = audiounit_setup_stream(stm.get()); } @@ -2543,7 +2582,7 @@ audiounit_close_stream(cubeb_stream *stm) stm->mixer.reset(); if (stm->aggregate_device_id) { - audiounit_destroy_aggregate_device(stm->plugin_id, stm->aggregate_device_id); + audiounit_destroy_aggregate_device(stm->plugin_id, &stm->aggregate_device_id); stm->aggregate_device_id = 0; } } @@ -2560,7 +2599,7 @@ audiounit_stream_destroy(cubeb_stream * stm) r = audiounit_uninstall_device_changed_callback(stm); if (r != CUBEB_OK) { - LOG("(%p) Could not uninstall the device changed callback", stm); + LOG("(%p) Could not uninstall all device change listeners", stm); } auto_lock context_lock(stm->context->mutex); @@ -2640,8 +2679,7 @@ audiounit_stream_stop(cubeb_stream * stm) static int audiounit_stream_get_position(cubeb_stream * stm, uint64_t * position) { - auto_lock lock(stm->mutex); - + assert(stm); *position = stm->frames_played; return CUBEB_OK; } @@ -2671,8 +2709,8 @@ audiounit_stream_get_latency(cubeb_stream * stm, uint32_t * latency) kAudioObjectPropertyElementMaster }; - r = audiounit_get_output_device_id(&output_device_id); - if (r != noErr) { + output_device_id = audiounit_get_default_device_id(CUBEB_DEVICE_TYPE_OUTPUT); + if (output_device_id == kAudioObjectUnknown) { return CUBEB_ERROR; } @@ -2795,7 +2833,8 @@ int audiounit_stream_get_current_device(cubeb_stream * stm, *device = NULL; - if (audiounit_get_output_device_id(&output_device_id) != CUBEB_OK) { + output_device_id = audiounit_get_default_device_id(CUBEB_DEVICE_TYPE_OUTPUT); + if (output_device_id == kAudioObjectUnknown) { return CUBEB_ERROR; } @@ -2829,7 +2868,8 @@ int audiounit_stream_get_current_device(cubeb_stream * stm, memcpy((*device)->output_name, strdata, size); (*device)->output_name[size] = '\0'; - if (audiounit_get_input_device_id(&input_device_id) != CUBEB_OK) { + input_device_id = audiounit_get_default_device_id(CUBEB_DEVICE_TYPE_INPUT); + if (input_device_id == kAudioObjectUnknown) { return CUBEB_ERROR; } @@ -2871,46 +2911,14 @@ int audiounit_stream_device_destroy(cubeb_stream * /* stream */, int audiounit_stream_register_device_changed_callback(cubeb_stream * stream, cubeb_device_changed_callback device_changed_callback) { + auto_lock dev_cb_lock(stream->device_changed_callback_lock); /* Note: second register without unregister first causes 'nope' error. * Current implementation requires unregister before register a new cb. */ assert(!stream->device_changed_callback); - - auto_lock lock(stream->mutex); - stream->device_changed_callback = device_changed_callback; - return CUBEB_OK; } -static OSStatus -audiounit_get_devices(std::vector & devices) -{ - OSStatus ret; - UInt32 size = 0; - AudioObjectPropertyAddress adr = { kAudioHardwarePropertyDevices, - kAudioObjectPropertyScopeGlobal, - kAudioObjectPropertyElementMaster }; - - ret = AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, &adr, 0, NULL, &size); - if (ret != noErr) { - return ret; - } - - uint32_t count = static_cast(size / sizeof(AudioObjectID)); - if (count == 0) { - return -1; - } - assert(devices.empty()); - devices.resize(count); - - ret = AudioObjectGetPropertyData(kAudioObjectSystemObject, &adr, 0, NULL, &size, devices.data()); - if (ret != noErr) { - devices.clear(); - } - - return ret; -} - static char * audiounit_strref_to_cstr_utf8(CFStringRef strref) { @@ -3117,22 +3125,30 @@ static int audiounit_enumerate_devices(cubeb * /* context */, cubeb_device_type type, cubeb_device_collection * collection) { - std::vector hwdevs; - uint32_t i; - OSStatus err; + std::vector input_devs; + std::vector output_devs; - err = audiounit_get_devices(hwdevs); - if (err != noErr) { - return CUBEB_ERROR; + // Count number of input and output devices. This is not + // necessarily the same as the count of raw devices supported by the + // system since, for example, with Soundflower installed, some + // devices may report as being both input *and* output and cubeb + // separates those into two different devices. + + if (type & CUBEB_DEVICE_TYPE_OUTPUT) { + output_devs = audiounit_get_devices_of_type(CUBEB_DEVICE_TYPE_OUTPUT); } - auto devices = new cubeb_device_info[hwdevs.size()]; + if (type & CUBEB_DEVICE_TYPE_INPUT) { + input_devs = audiounit_get_devices_of_type(CUBEB_DEVICE_TYPE_INPUT); + } + + auto devices = new cubeb_device_info[output_devs.size() + input_devs.size()]; collection->count = 0; if (type & CUBEB_DEVICE_TYPE_OUTPUT) { - for (i = 0; i < hwdevs.size(); i++) { + for (auto dev: output_devs) { auto device = &devices[collection->count]; - auto err = audiounit_create_device_from_hwdev(device, hwdevs[i], CUBEB_DEVICE_TYPE_OUTPUT); + auto err = audiounit_create_device_from_hwdev(device, dev, CUBEB_DEVICE_TYPE_OUTPUT); if (err != CUBEB_OK) { continue; } @@ -3141,9 +3157,9 @@ audiounit_enumerate_devices(cubeb * /* context */, cubeb_device_type type, } if (type & CUBEB_DEVICE_TYPE_INPUT) { - for (i = 0; i < hwdevs.size(); i++) { + for (auto dev: input_devs) { auto device = &devices[collection->count]; - auto err = audiounit_create_device_from_hwdev(device, hwdevs[i], CUBEB_DEVICE_TYPE_INPUT); + auto err = audiounit_create_device_from_hwdev(device, dev, CUBEB_DEVICE_TYPE_INPUT); if (err != CUBEB_OK) { continue; } @@ -3339,6 +3355,7 @@ cubeb_ops const audiounit_ops = { /*.stream_destroy =*/ audiounit_stream_destroy, /*.stream_start =*/ audiounit_stream_start, /*.stream_stop =*/ audiounit_stream_stop, + /*.stream_reset_default_device =*/ nullptr, /*.stream_get_position =*/ audiounit_stream_get_position, /*.stream_get_latency =*/ audiounit_stream_get_latency, /*.stream_set_volume =*/ audiounit_stream_set_volume, diff --git a/Externals/cubeb/src/cubeb_jack.cpp b/Externals/cubeb/src/cubeb_jack.cpp index 5b65c78237..c49f6bcb0b 100644 --- a/Externals/cubeb/src/cubeb_jack.cpp +++ b/Externals/cubeb/src/cubeb_jack.cpp @@ -96,6 +96,8 @@ static int cbjack_stream_device_destroy(cubeb_stream * stream, static int cbjack_stream_get_current_device(cubeb_stream * stm, cubeb_device ** const device); static int cbjack_enumerate_devices(cubeb * context, cubeb_device_type type, cubeb_device_collection * collection); +static int cbjack_device_collection_destroy(cubeb * context, + cubeb_device_collection * collection); static int cbjack_stream_init(cubeb * context, cubeb_stream ** stream, char const * stream_name, cubeb_devid input_device, cubeb_stream_params * input_stream_params, @@ -119,12 +121,13 @@ static struct cubeb_ops const cbjack_ops = { .get_preferred_sample_rate = cbjack_get_preferred_sample_rate, .get_preferred_channel_layout = NULL, .enumerate_devices = cbjack_enumerate_devices, - .device_collection_destroy = cubeb_utils_default_device_collection_destroy, + .device_collection_destroy = cbjack_device_collection_destroy, .destroy = cbjack_destroy, .stream_init = cbjack_stream_init, .stream_destroy = cbjack_stream_destroy, .stream_start = cbjack_stream_start, .stream_stop = cbjack_stream_stop, + .stream_reset_default_device = NULL, .stream_get_position = cbjack_stream_get_position, .stream_get_latency = cbjack_get_latency, .stream_set_volume = cbjack_stream_set_volume, @@ -973,6 +976,9 @@ cbjack_stream_device_destroy(cubeb_stream * /*stream*/, return CUBEB_OK; } +#define JACK_DEFAULT_IN "JACK capture" +#define JACK_DEFAULT_OUT "JACK playback" + static int cbjack_enumerate_devices(cubeb * context, cubeb_device_type type, cubeb_device_collection * collection) @@ -982,20 +988,20 @@ cbjack_enumerate_devices(cubeb * context, cubeb_device_type type, uint32_t rate; cbjack_get_preferred_sample_rate(context, &rate); - const char * j_in = "JACK capture"; - const char * j_out = "JACK playback"; cubeb_device_info * devices = new cubeb_device_info[2]; - reinterpret_cast(calloc(2, sizeof(cubeb_device_info))); + if (!devices) + return CUBEB_ERROR; + PodZero(devices, 2); collection->count = 0; if (type & CUBEB_DEVICE_TYPE_OUTPUT) { cubeb_device_info * cur = &devices[collection->count]; - cur->device_id = strdup(j_out); + cur->device_id = JACK_DEFAULT_OUT; cur->devid = (cubeb_devid) cur->device_id; - cur->friendly_name = strdup(j_out); - cur->group_id = strdup(j_out); - cur->vendor_name = strdup(j_out); + cur->friendly_name = JACK_DEFAULT_OUT; + cur->group_id = JACK_DEFAULT_OUT; + cur->vendor_name = JACK_DEFAULT_OUT; cur->type = CUBEB_DEVICE_TYPE_OUTPUT; cur->state = CUBEB_DEVICE_STATE_ENABLED; cur->preferred = CUBEB_DEVICE_PREF_ALL; @@ -1012,11 +1018,11 @@ cbjack_enumerate_devices(cubeb * context, cubeb_device_type type, if (type & CUBEB_DEVICE_TYPE_INPUT) { cubeb_device_info * cur = &devices[collection->count]; - cur->device_id = strdup(j_in); + cur->device_id = JACK_DEFAULT_IN; cur->devid = (cubeb_devid) cur->device_id; - cur->friendly_name = strdup(j_in); - cur->group_id = strdup(j_in); - cur->vendor_name = strdup(j_in); + cur->friendly_name = JACK_DEFAULT_IN; + cur->group_id = JACK_DEFAULT_IN; + cur->vendor_name = JACK_DEFAULT_IN; cur->type = CUBEB_DEVICE_TYPE_INPUT; cur->state = CUBEB_DEVICE_STATE_ENABLED; cur->preferred = CUBEB_DEVICE_PREF_ALL; @@ -1035,3 +1041,12 @@ cbjack_enumerate_devices(cubeb * context, cubeb_device_type type, return CUBEB_OK; } + +static int +cbjack_device_collection_destroy(cubeb * /*ctx*/, + cubeb_device_collection * collection) +{ + XASSERT(collection); + delete [] collection->device; + return CUBEB_OK; +} diff --git a/Externals/cubeb/src/cubeb_kai.c b/Externals/cubeb/src/cubeb_kai.c index f6a79be71e..4ce0ae72e5 100644 --- a/Externals/cubeb/src/cubeb_kai.c +++ b/Externals/cubeb/src/cubeb_kai.c @@ -351,6 +351,7 @@ static struct cubeb_ops const kai_ops = { /*.stream_destroy =*/ kai_stream_destroy, /*.stream_start =*/ kai_stream_start, /*.stream_stop =*/ kai_stream_stop, + /*.stream_reset_default_device =*/ NULL, /*.stream_get_position =*/ kai_stream_get_position, /*.stream_get_latency = */ kai_stream_get_latency, /*.stream_set_volume =*/ kai_stream_set_volume, diff --git a/Externals/cubeb/src/cubeb_log.cpp b/Externals/cubeb/src/cubeb_log.cpp index 4c6169354a..54c7f4a154 100644 --- a/Externals/cubeb/src/cubeb_log.cpp +++ b/Externals/cubeb/src/cubeb_log.cpp @@ -95,6 +95,12 @@ public: } }).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 = { @@ -128,3 +134,11 @@ void cubeb_async_log(char const * fmt, ...) cubeb_async_logger::get().push(msg); va_end(args); } + +void cubeb_async_log_reset_threads() +{ + if (!g_cubeb_log_callback) { + return; + } + cubeb_async_logger::get().reset_producer_thread(); +} diff --git a/Externals/cubeb/src/cubeb_log.h b/Externals/cubeb/src/cubeb_log.h index 5ca1462a13..a79976bb3f 100644 --- a/Externals/cubeb/src/cubeb_log.h +++ b/Externals/cubeb/src/cubeb_log.h @@ -23,6 +23,7 @@ extern "C" { 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, ...); +void cubeb_async_log_reset_threads(); #ifdef __cplusplus } diff --git a/Externals/cubeb/src/cubeb_mixer.cpp b/Externals/cubeb/src/cubeb_mixer.cpp index 375f12402f..08787f1583 100644 --- a/Externals/cubeb/src/cubeb_mixer.cpp +++ b/Externals/cubeb/src/cubeb_mixer.cpp @@ -299,7 +299,7 @@ mix_remap(long inframes, T * out, unsigned long out_len, cubeb_channel_layout in_layout, cubeb_channel_layout out_layout) { - assert(in_layout != out_layout); + assert(in_layout != out_layout && inframes >= 0); // We might overwrite the data before we copied them to the mapped index // (e.g. upmixing from stereo to 2F1 or mapping [L, R] to [R, L]) @@ -325,15 +325,16 @@ mix_remap(long inframes, return false; } - for (unsigned long i = 0, out_index = 0; i < inframes * in_channels; i += in_channels, out_index += out_channels) { + for (unsigned long i = 0, out_index = 0; i < (unsigned long)inframes * in_channels; i += in_channels, out_index += out_channels) { for (unsigned int j = 0; j < out_channels; ++j) { cubeb_channel channel = CHANNEL_INDEX_TO_ORDER[out_layout][j]; uint32_t channel_mask = 1 << channel; int channel_index = CHANNEL_ORDER_TO_INDEX[in_layout][channel]; + assert(channel_index >= -1); assert(out_index + j < out_len); if (in_layout_mask & channel_mask) { - assert(i + channel_index < in_len); assert(channel_index != -1); + assert(i + channel_index < in_len); out[out_index + j] = in[i + channel_index]; } else { assert(channel_index == -1); @@ -353,13 +354,13 @@ downmix_fallback(long inframes, T * out, unsigned long out_len, unsigned int in_channels, unsigned int out_channels) { - assert(in_channels >= out_channels); + assert(in_channels >= out_channels && inframes >= 0); if (in_channels == out_channels && in == out) { return; } - for (unsigned long i = 0, out_index = 0; i < inframes * in_channels; i += in_channels, out_index += out_channels) { + for (unsigned long i = 0, out_index = 0; i < (unsigned long)inframes * in_channels; i += in_channels, out_index += out_channels) { for (unsigned int j = 0; j < out_channels; ++j) { assert(i + j < in_len && out_index + j < out_len); out[out_index + j] = in[i + j]; @@ -416,7 +417,7 @@ mono_to_stereo(long insamples, T const * in, unsigned long in_len, T * out, unsigned long out_len, unsigned int out_channels) { for (long i = 0, j = 0; i < insamples; ++i, j += out_channels) { - assert(i < in_len && j + 1 < out_len); + assert((unsigned long)i < in_len && (unsigned long)j + 1 < out_len); out[j] = out[j + 1] = in[i]; } } @@ -460,7 +461,7 @@ cubeb_upmix(long inframes, /* Put silence in remaining channels. */ for (long i = 0, o = 0; i < inframes; ++i, o += out_channels) { for (unsigned int j = 2; j < out_channels; ++j) { - assert(o + j < out_len); + assert((unsigned long)o + j < out_len); out[o + j] = 0.0; } } @@ -490,7 +491,8 @@ cubeb_should_downmix(cubeb_stream_params const * stream, cubeb_stream_params con bool cubeb_should_mix(cubeb_stream_params const * stream, cubeb_stream_params const * mixer) { - return cubeb_should_upmix(stream, mixer) || cubeb_should_downmix(stream, mixer); + return stream->layout != CUBEB_LAYOUT_UNDEFINED && + (cubeb_should_upmix(stream, mixer) || cubeb_should_downmix(stream, mixer)); } struct cubeb_mixer { @@ -559,11 +561,11 @@ void cubeb_mixer_destroy(cubeb_mixer * mixer) void cubeb_mixer_mix(cubeb_mixer * mixer, long frames, void * input_buffer, unsigned long input_buffer_length, - void * output_buffer, unsigned long outputput_buffer_length, + void * output_buffer, unsigned long output_buffer_length, cubeb_stream_params const * stream_params, cubeb_stream_params const * mixer_params) { assert(mixer); - mixer->mix(frames, input_buffer, input_buffer_length, output_buffer, outputput_buffer_length, + mixer->mix(frames, input_buffer, input_buffer_length, output_buffer, output_buffer_length, stream_params, mixer_params); } diff --git a/Externals/cubeb/src/cubeb_mixer.h b/Externals/cubeb/src/cubeb_mixer.h index eeb69f6f91..51fb9088a8 100644 --- a/Externals/cubeb/src/cubeb_mixer.h +++ b/Externals/cubeb/src/cubeb_mixer.h @@ -79,7 +79,7 @@ cubeb_mixer * cubeb_mixer_create(cubeb_sample_format format, void cubeb_mixer_destroy(cubeb_mixer * mixer); void cubeb_mixer_mix(cubeb_mixer * mixer, long frames, void * input_buffer, unsigned long input_buffer_length, - void * output_buffer, unsigned long outputput_buffer_length, + void * output_buffer, unsigned long output_buffer_length, cubeb_stream_params const * stream_params, cubeb_stream_params const * mixer_params); diff --git a/Externals/cubeb/src/cubeb_opensl.c b/Externals/cubeb/src/cubeb_opensl.c index 335c095e45..903906d046 100644 --- a/Externals/cubeb/src/cubeb_opensl.c +++ b/Externals/cubeb/src/cubeb_opensl.c @@ -105,7 +105,6 @@ struct cubeb_stream { /* Flag indicating draining. Synchronized * by stream::mutex lock. */ int draining; - cubeb_stream_type stream_type; /* Flags to determine in/out.*/ uint32_t input_enabled; uint32_t output_enabled; @@ -187,6 +186,29 @@ opensl_set_draining(cubeb_stream * stm, int value) stm->draining = value; } +static void +opensl_notify_drained(cubeb_stream * stm) +{ + assert(stm); + int r = pthread_mutex_lock(&stm->mutex); + assert(r == 0); + int draining = opensl_get_draining(stm); + r = pthread_mutex_unlock(&stm->mutex); + assert(r == 0); + if (draining) { + stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_DRAINED); + if (stm->play) { + LOG("stop player in play_callback"); + r = opensl_stop_player(stm); + assert(r == CUBEB_OK); + } + if (stm->recorderItf) { + r = opensl_stop_recorder(stm); + assert(r == CUBEB_OK); + } + } +} + static uint32_t opensl_get_shutdown(cubeb_stream * stm) { @@ -217,24 +239,7 @@ play_callback(SLPlayItf caller, void * user_ptr, SLuint32 event) assert(stm); switch (event) { case SL_PLAYEVENT_HEADATMARKER: - { - int r = pthread_mutex_lock(&stm->mutex); - assert(r == 0); - draining = opensl_get_draining(stm); - r = pthread_mutex_unlock(&stm->mutex); - assert(r == 0); - if (draining) { - stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_DRAINED); - if (stm->play) { - r = opensl_stop_player(stm); - assert(r == CUBEB_OK); - } - if (stm->recorderItf) { - r = opensl_stop_recorder(stm); - assert(r == CUBEB_OK); - } - } - } + opensl_notify_drained(stm); break; default: break; @@ -330,9 +335,16 @@ bufferqueue_callback(SLBufferQueueItf caller, void * user_ptr) opensl_set_draining(stm, 1); r = pthread_mutex_unlock(&stm->mutex); assert(r == 0); - // Use SL_PLAYEVENT_HEADATMARKER event from slPlayCallback of SLPlayItf - // to make sure all the data has been processed. - (*stm->play)->SetMarkerPosition(stm->play, (SLmillisecond)written_duration); + + if (written_duration == 0) { + // since we didn't write any sample, it's not possible to reach the marker + // time and trigger the callback. We should initiative notify drained. + opensl_notify_drained(stm); + } else { + // Use SL_PLAYEVENT_HEADATMARKER event from slPlayCallback of SLPlayItf + // to make sure all the data has been processed. + (*stm->play)->SetMarkerPosition(stm->play, (SLmillisecond)written_duration); + } return; } } @@ -586,31 +598,6 @@ player_fullduplex_callback(SLBufferQueueItf caller, void * user_ptr) TIMESTAMP("EXIT"); } -#if defined(__ANDROID__) -static SLuint32 -convert_stream_type_to_sl_stream(cubeb_stream_type stream_type) -{ - switch(stream_type) { - case CUBEB_STREAM_TYPE_SYSTEM: - return SL_ANDROID_STREAM_SYSTEM; - case CUBEB_STREAM_TYPE_MUSIC: - return SL_ANDROID_STREAM_MEDIA; - case CUBEB_STREAM_TYPE_NOTIFICATION: - return SL_ANDROID_STREAM_NOTIFICATION; - case CUBEB_STREAM_TYPE_ALARM: - return SL_ANDROID_STREAM_ALARM; - case CUBEB_STREAM_TYPE_VOICE_CALL: - return SL_ANDROID_STREAM_VOICE; - case CUBEB_STREAM_TYPE_RING: - return SL_ANDROID_STREAM_RING; - case CUBEB_STREAM_TYPE_SYSTEM_ENFORCED: - return SL_ANDROID_STREAM_SYSTEM_ENFORCED; - default: - return 0xFFFFFFFF; - } -} -#endif - static void opensl_destroy(cubeb * ctx); #if defined(__ANDROID__) @@ -899,7 +886,7 @@ opensl_get_min_latency(cubeb * ctx, cubeb_stream_params params, uint32_t * laten if (get_primary_output_frame_count) { primary_buffer_size = get_primary_output_frame_count(); } else { - if (get_output_frame_count(&primary_buffer_size, params.stream_type) != 0) { + if (get_output_frame_count(&primary_buffer_size, AUDIO_STREAM_TYPE_MUSIC) != 0) { return CUBEB_ERROR; } } @@ -1127,7 +1114,6 @@ opensl_configure_playback(cubeb_stream * stm, cubeb_stream_params * params) { assert(params); stm->inputrate = params->rate; - stm->stream_type = params->stream_type; stm->framesize = params->channels * sizeof(int16_t); stm->lastPosition = -1; stm->lastPositionTimeStamp = 0; @@ -1228,29 +1214,6 @@ opensl_configure_playback(cubeb_stream * stm, cubeb_stream_params * params) { assert(stm->queuebuf[i]); } -#if defined(__ANDROID__) - SLuint32 stream_type = convert_stream_type_to_sl_stream(params->stream_type); - if (stream_type != 0xFFFFFFFF) { - SLAndroidConfigurationItf playerConfig; - res = (*stm->playerObj)->GetInterface(stm->playerObj, - stm->context->SL_IID_ANDROIDCONFIGURATION, - &playerConfig); - if (res != SL_RESULT_SUCCESS) { - LOG("Failed to get android configuration interface. Error code: %lu", res); - return CUBEB_ERROR; - } - - res = (*playerConfig)->SetConfiguration(playerConfig, - SL_ANDROID_KEY_STREAM_TYPE, - &stream_type, - sizeof(SLint32)); - if (res != SL_RESULT_SUCCESS) { - LOG("Failed to set android configuration interface. Error code: %lu", res); - return CUBEB_ERROR; - } - } -#endif - res = (*stm->playerObj)->Realize(stm->playerObj, SL_BOOLEAN_FALSE); if (res != SL_RESULT_SUCCESS) { LOG("Failed to realize player object. Error code: %lu", res); @@ -1654,7 +1617,7 @@ opensl_stream_get_position(cubeb_stream * stm, uint64_t * position) samplerate = stm->inputrate; - r = stm->context->get_output_latency(&mixer_latency, stm->stream_type); + r = stm->context->get_output_latency(&mixer_latency, AUDIO_STREAM_TYPE_MUSIC); if (r) { return CUBEB_ERROR; } @@ -1690,7 +1653,7 @@ opensl_stream_get_latency(cubeb_stream * stm, uint32_t * latency) uint32_t mixer_latency; // The latency returned by AudioFlinger is in ms. /* audio_stream_type_t is an int, so this is okay. */ - r = stm->context->get_output_latency(&mixer_latency, stm->stream_type); + r = stm->context->get_output_latency(&mixer_latency, AUDIO_STREAM_TYPE_MUSIC); if (r) { return CUBEB_ERROR; } @@ -1746,6 +1709,7 @@ static struct cubeb_ops const opensl_ops = { .stream_destroy = opensl_stream_destroy, .stream_start = opensl_stream_start, .stream_stop = opensl_stream_stop, + .stream_reset_default_device = NULL, .stream_get_position = opensl_stream_get_position, .stream_get_latency = opensl_stream_get_latency, .stream_set_volume = opensl_stream_set_volume, diff --git a/Externals/cubeb/src/cubeb_pulse.c b/Externals/cubeb/src/cubeb_pulse.c index 7efc6bf6e0..73307cd03b 100644 --- a/Externals/cubeb/src/cubeb_pulse.c +++ b/Externals/cubeb/src/cubeb_pulse.c @@ -13,7 +13,7 @@ #include "cubeb/cubeb.h" #include "cubeb-internal.h" #include "cubeb_mixer.h" -#include "cubeb_utils.h" +#include "cubeb_strings.h" #include #ifdef DISABLE_LIBPULSE_DLOPEN @@ -102,6 +102,7 @@ struct cubeb { int error; cubeb_device_collection_changed_callback collection_changed_callback; void * collection_changed_user_ptr; + cubeb_strings * device_ids; }; struct cubeb_stream { @@ -127,6 +128,24 @@ enum cork_state { NOTIFY = 1 << 1 }; +static int +intern_device_id(cubeb * ctx, char const ** id) +{ + char const * interned; + + assert(ctx); + assert(id); + + interned = cubeb_strings_intern(ctx->device_ids, *id); + if (!interned) { + return CUBEB_ERROR; + } + + *id = interned; + + return CUBEB_OK; +} + static void sink_info_callback(pa_context * context, const pa_sink_info * info, int eol, void * u) { @@ -608,6 +627,10 @@ pulse_init(cubeb ** context, char const * context_name) ctx->ops = &pulse_ops; ctx->libpulse = libpulse; + if (cubeb_strings_init(&ctx->device_ids) != CUBEB_OK) { + pulse_destroy(ctx); + return CUBEB_ERROR; + } ctx->mainloop = WRAP(pa_threaded_mainloop_new)(); ctx->default_sink_info = NULL; @@ -630,7 +653,6 @@ pulse_init(cubeb ** context, char const * context_name) WRAP(pa_operation_unref)(o); } WRAP(pa_threaded_mainloop_unlock)(ctx->mainloop); - assert(ctx->default_sink_info); *context = ctx; @@ -650,6 +672,9 @@ pulse_get_max_channel_count(cubeb * ctx, uint32_t * max_channels) (void)ctx; assert(ctx && max_channels); + if (!ctx->default_sink_info) + return CUBEB_ERROR; + *max_channels = ctx->default_sink_info->channel_map.channels; return CUBEB_OK; @@ -661,6 +686,9 @@ pulse_get_preferred_sample_rate(cubeb * ctx, uint32_t * rate) assert(ctx && rate); (void)ctx; + if (!ctx->default_sink_info) + return CUBEB_ERROR; + *rate = ctx->default_sink_info->sample_spec.rate; return CUBEB_OK; @@ -672,6 +700,9 @@ pulse_get_preferred_channel_layout(cubeb * ctx, cubeb_channel_layout * layout) assert(ctx && layout); (void)ctx; + if (!ctx->default_sink_info) + return CUBEB_ERROR; + *layout = channel_map_to_layout(&ctx->default_sink_info->channel_map); return CUBEB_OK; @@ -717,6 +748,10 @@ pulse_destroy(cubeb * ctx) WRAP(pa_threaded_mainloop_free)(ctx->mainloop); } + if (ctx->device_ids) { + cubeb_strings_destroy(ctx->device_ids); + } + if (ctx->libpulse) { dlclose(ctx->libpulse); } @@ -751,8 +786,9 @@ create_pa_stream(cubeb_stream * stm, { assert(stm && stream_params); assert(&stm->input_stream == pa_stm || (&stm->output_stream == pa_stm && - stream_params->layout != CUBEB_LAYOUT_UNDEFINED && - CUBEB_CHANNEL_LAYOUT_MAPS[stream_params->layout].channels == stream_params->channels)); + (stream_params->layout == CUBEB_LAYOUT_UNDEFINED || + (stream_params->layout != CUBEB_LAYOUT_UNDEFINED && + CUBEB_CHANNEL_LAYOUT_MAPS[stream_params->layout].channels == stream_params->channels)))); *pa_stm = NULL; pa_sample_spec ss; ss.format = to_pulse_format(stream_params->format); @@ -1049,6 +1085,7 @@ pulse_stream_set_volume(cubeb_stream * stm, float volume) pa_volume_t vol; pa_cvolume cvol; const pa_sample_spec * ss; + cubeb * ctx; if (!stm->output_stream) { return CUBEB_ERROR; @@ -1058,7 +1095,9 @@ pulse_stream_set_volume(cubeb_stream * stm, float volume) /* if the pulse daemon is configured to use flat volumes, * apply our own gain instead of changing the input volume on the sink. */ - if (stm->context->default_sink_info->flags & PA_SINK_FLAT_VOLUME) { + ctx = stm->context; + if (ctx->default_sink_info && + (ctx->default_sink_info->flags & PA_SINK_FLAT_VOLUME)) { stm->volume = volume; } else { ss = WRAP(pa_stream_get_sample_spec)(stm->output_stream); @@ -1068,16 +1107,16 @@ pulse_stream_set_volume(cubeb_stream * stm, float volume) index = WRAP(pa_stream_get_index)(stm->output_stream); - op = WRAP(pa_context_set_sink_input_volume)(stm->context->context, + op = WRAP(pa_context_set_sink_input_volume)(ctx->context, index, &cvol, volume_success, stm); if (op) { - operation_wait(stm->context, stm->output_stream, op); + operation_wait(ctx, stm->output_stream, op); WRAP(pa_operation_unref)(op); } } - WRAP(pa_threaded_mainloop_unlock)(stm->context->mainloop); + WRAP(pa_threaded_mainloop_unlock)(ctx->mainloop); return CUBEB_OK; } @@ -1202,18 +1241,30 @@ pulse_sink_info_cb(pa_context * context, const pa_sink_info * info, { pulse_dev_list_data * list_data = user_data; cubeb_device_info * devinfo; - const char * prop; + char const * prop = NULL; + char const * device_id = NULL; (void)context; - if (eol || info == NULL) + if (eol) { + WRAP(pa_threaded_mainloop_signal)(list_data->context->mainloop, 0); return; + } + + if (info == NULL) + return; + + device_id = info->name; + if (intern_device_id(list_data->context, &device_id) != CUBEB_OK) { + assert(false); + return; + } pulse_ensure_dev_list_data_list_size(list_data); devinfo = &list_data->devinfo[list_data->count]; memset(devinfo, 0, sizeof(cubeb_device_info)); - devinfo->device_id = strdup(info->name); + devinfo->device_id = device_id; devinfo->devid = (cubeb_devid) devinfo->device_id; devinfo->friendly_name = strdup(info->description); prop = WRAP(pa_proplist_gets)(info->proplist, "sysfs.path"); @@ -1239,8 +1290,6 @@ pulse_sink_info_cb(pa_context * context, const pa_sink_info * info, devinfo->latency_hi = 0; list_data->count += 1; - - WRAP(pa_threaded_mainloop_signal)(list_data->context->mainloop, 0); } static cubeb_device_state @@ -1264,18 +1313,27 @@ pulse_source_info_cb(pa_context * context, const pa_source_info * info, { pulse_dev_list_data * list_data = user_data; cubeb_device_info * devinfo; - const char * prop; + char const * prop = NULL; + char const * device_id = NULL; (void)context; - if (eol) + if (eol) { + WRAP(pa_threaded_mainloop_signal)(list_data->context->mainloop, 0); return; + } + + device_id = info->name; + if (intern_device_id(list_data->context, &device_id) != CUBEB_OK) { + assert(false); + return; + } pulse_ensure_dev_list_data_list_size(list_data); devinfo = &list_data->devinfo[list_data->count]; memset(devinfo, 0, sizeof(cubeb_device_info)); - devinfo->device_id = strdup(info->name); + devinfo->device_id = device_id; devinfo->devid = (cubeb_devid) devinfo->device_id; devinfo->friendly_name = strdup(info->description); prop = WRAP(pa_proplist_gets)(info->proplist, "sysfs.path"); @@ -1301,7 +1359,6 @@ pulse_source_info_cb(pa_context * context, const pa_source_info * info, devinfo->latency_hi = 0; list_data->count += 1; - WRAP(pa_threaded_mainloop_signal)(list_data->context->mainloop, 0); } static void @@ -1313,8 +1370,10 @@ pulse_server_info_cb(pa_context * c, const pa_server_info * i, void * userdata) free(list_data->default_sink_name); free(list_data->default_source_name); - list_data->default_sink_name = strdup(i->default_sink_name); - list_data->default_source_name = strdup(i->default_source_name); + list_data->default_sink_name = + i->default_sink_name ? strdup(i->default_sink_name) : NULL; + list_data->default_source_name = + i->default_source_name ? strdup(i->default_source_name) : NULL; WRAP(pa_threaded_mainloop_signal)(list_data->context->mainloop, 0); } @@ -1363,6 +1422,21 @@ pulse_enumerate_devices(cubeb * context, cubeb_device_type type, return CUBEB_OK; } +static int +pulse_device_collection_destroy(cubeb * ctx, cubeb_device_collection * collection) +{ + size_t n; + + for (n = 0; n < collection->count; n++) { + free((void *) collection->device[n].friendly_name); + free((void *) collection->device[n].vendor_name); + free((void *) collection->device[n].group_id); + } + + free(collection->device); + return CUBEB_OK; +} + static int pulse_stream_get_current_device(cubeb_stream * stm, cubeb_device ** const device) { @@ -1492,12 +1566,13 @@ static struct cubeb_ops const pulse_ops = { .get_preferred_sample_rate = pulse_get_preferred_sample_rate, .get_preferred_channel_layout = pulse_get_preferred_channel_layout, .enumerate_devices = pulse_enumerate_devices, - .device_collection_destroy = cubeb_utils_default_device_collection_destroy, + .device_collection_destroy = pulse_device_collection_destroy, .destroy = pulse_destroy, .stream_init = pulse_stream_init, .stream_destroy = pulse_stream_destroy, .stream_start = pulse_stream_start, .stream_stop = pulse_stream_stop, + .stream_reset_default_device = NULL, .stream_get_position = pulse_stream_get_position, .stream_get_latency = pulse_stream_get_latency, .stream_set_volume = pulse_stream_set_volume, diff --git a/Externals/cubeb/src/cubeb_resampler.cpp b/Externals/cubeb/src/cubeb_resampler.cpp index 65dd0a86a0..786d01341f 100644 --- a/Externals/cubeb/src/cubeb_resampler.cpp +++ b/Externals/cubeb/src/cubeb_resampler.cpp @@ -35,15 +35,22 @@ to_speex_quality(cubeb_resampler_quality q) } } +uint32_t min_buffered_audio_frame(uint32_t sample_rate) +{ + return sample_rate / 20; +} + template passthrough_resampler::passthrough_resampler(cubeb_stream * s, cubeb_data_callback cb, void * ptr, - uint32_t input_channels) + uint32_t input_channels, + uint32_t sample_rate) : processor(input_channels) , stream(s) , data_callback(cb) , user_ptr(ptr) + , sample_rate(sample_rate) { } @@ -73,6 +80,7 @@ long passthrough_resampler::fill(void * input_buffer, long * input_frames_cou if (input_buffer) { internal_input_buffer.pop(nullptr, frames_to_samples(output_frames)); *input_frames_count = output_frames; + drop_audio_if_needed(); } return rv; @@ -242,9 +250,15 @@ cubeb_resampler_speex output_processor->written(got); + input_processor->drop_audio_if_needed(); + /* Process the output. If not enough frames have been returned from the * callback, drain the processors. */ - return output_processor->output(out_buffer, output_frames_needed); + got = output_processor->output(out_buffer, output_frames_needed); + + output_processor->drop_audio_if_needed(); + + return got; } /* Resampler C API */ diff --git a/Externals/cubeb/src/cubeb_resampler_internal.h b/Externals/cubeb/src/cubeb_resampler_internal.h index bb5da05723..ca08ec5714 100644 --- a/Externals/cubeb/src/cubeb_resampler_internal.h +++ b/Externals/cubeb/src/cubeb_resampler_internal.h @@ -39,6 +39,13 @@ MOZ_END_STD_NAMESPACE /* This header file contains the internal C++ API of the resamplers, for testing. */ +// When dropping audio input frames to prevent building +// an input delay, this function returns the number of frames +// to keep in the buffer. +// @parameter sample_rate The sample rate of the stream. +// @return A number of frames to keep. +uint32_t min_buffered_audio_frame(uint32_t sample_rate); + int to_speex_quality(cubeb_resampler_quality q); struct cubeb_resampler { @@ -75,7 +82,8 @@ public: passthrough_resampler(cubeb_stream * s, cubeb_data_callback cb, void * ptr, - uint32_t input_channels); + uint32_t input_channels, + uint32_t sample_rate); virtual long fill(void * input_buffer, long * input_frames_count, void * output_buffer, long output_frames); @@ -85,6 +93,15 @@ public: return 0; } + void drop_audio_if_needed() + { + uint32_t to_keep = min_buffered_audio_frame(sample_rate); + uint32_t available = samples_to_frames(internal_input_buffer.length()); + if (available > to_keep) { + internal_input_buffer.pop(nullptr, frames_to_samples(available - to_keep)); + } + } + private: cubeb_stream * const stream; const cubeb_data_callback data_callback; @@ -92,6 +109,7 @@ private: /* This allows to buffer some input to account for the fact that we buffer * some inputs. */ auto_array internal_input_buffer; + uint32_t sample_rate; }; /** Bidirectional resampler, can resample an input and an output stream, or just @@ -164,6 +182,7 @@ public: int quality) : processor(channels) , resampling_ratio(static_cast(source_rate) / target_rate) + , source_rate(source_rate) , additional_latency(0) , leftover_samples(0) { @@ -296,6 +315,16 @@ public: resampling_in_buffer.set_length(leftover_samples + frames_to_samples(written_frames)); } + + void drop_audio_if_needed() + { + // Keep at most 100ms buffered. + uint32_t available = samples_to_frames(resampling_in_buffer.length()); + uint32_t to_keep = min_buffered_audio_frame(source_rate); + if (available > to_keep) { + resampling_in_buffer.pop(nullptr, frames_to_samples(available - to_keep)); + } + } private: /** Wrapper for the speex resampling functions to have a typed * interface. */ @@ -332,6 +361,7 @@ private: SpeexResamplerState * speex_resampler; /** Source rate / target rate. */ const float resampling_ratio; + const uint32_t source_rate; /** Storage for the input frames, to be resampled. Also contains * any unresampled frames after resampling. */ auto_array resampling_in_buffer; @@ -350,11 +380,13 @@ class delay_line : public processor { public: /** Constructor * @parameter frames the number of frames of delay. - * @parameter channels the number of channels of this delay line. */ - delay_line(uint32_t frames, uint32_t channels) + * @parameter channels the number of channels of this delay line. + * @parameter sample_rate sample-rate of the audio going through this delay line */ + delay_line(uint32_t frames, uint32_t channels, uint32_t sample_rate) : processor(channels) , length(frames) , leftover_samples(0) + , sample_rate(sample_rate) { /* Fill the delay line with some silent frames to add latency. */ delay_input_buffer.push_silence(frames * channels); @@ -444,6 +476,15 @@ public: { return length; } + + void drop_audio_if_needed() + { + size_t available = samples_to_frames(delay_input_buffer.length()); + uint32_t to_keep = min_buffered_audio_frame(sample_rate); + if (available > to_keep) { + delay_input_buffer.pop(nullptr, frames_to_samples(available - to_keep)); + } + } private: /** The length, in frames, of this delay line */ uint32_t length; @@ -455,6 +496,7 @@ private: /** The output buffer. This is only ever used if using the ::output with a * single argument. */ auto_array delay_output_buffer; + uint32_t sample_rate; }; /** This sits behind the C API and is more typed. */ @@ -485,7 +527,8 @@ cubeb_resampler_create_internal(cubeb_stream * stream, (output_params && !input_params && (output_params->rate == target_rate))) { return new passthrough_resampler(stream, callback, user_ptr, - input_params ? input_params->channels : 0); + input_params ? input_params->channels : 0, + target_rate); } /* Determine if we need to resampler one or both directions, and create the @@ -517,13 +560,15 @@ cubeb_resampler_create_internal(cubeb_stream * stream, * other direction so that the streams are synchronized. */ if (input_resampler && !output_resampler && input_params && output_params) { output_delay.reset(new delay_line(input_resampler->latency(), - output_params->channels)); + output_params->channels, + output_params->rate)); if (!output_delay) { return NULL; } } else if (output_resampler && !input_resampler && input_params && output_params) { input_delay.reset(new delay_line(output_resampler->latency(), - input_params->channels)); + input_params->channels, + output_params->rate)); if (!input_delay) { return NULL; } diff --git a/Externals/cubeb/src/cubeb_ringbuffer.h b/Externals/cubeb/src/cubeb_ringbuffer.h index ac5bf8c68b..b6696e886d 100644 --- a/Externals/cubeb/src/cubeb_ringbuffer.h +++ b/Externals/cubeb/src/cubeb_ringbuffer.h @@ -226,6 +226,17 @@ public: { return storage_capacity() - 1; } + /** + * Reset the consumer and producer thread identifier, in case the thread are + * being changed. This has to be externally synchronized. This is no-op when + * asserts are disabled. + */ + void reset_thread_ids() + { +#ifndef NDEBUG + consumer_id = producer_id = std::thread::id(); +#endif + } private: /** Return true if the ring buffer is empty. * diff --git a/Externals/cubeb/src/cubeb_sndio.c b/Externals/cubeb/src/cubeb_sndio.c index e6a7b987d0..8b359842b6 100644 --- a/Externals/cubeb/src/cubeb_sndio.c +++ b/Externals/cubeb/src/cubeb_sndio.c @@ -376,6 +376,7 @@ static struct cubeb_ops const sndio_ops = { .stream_destroy = sndio_stream_destroy, .stream_start = sndio_stream_start, .stream_stop = sndio_stream_stop, + .stream_reset_default_device = NULL, .stream_get_position = sndio_stream_get_position, .stream_get_latency = sndio_stream_get_latency, .stream_set_volume = sndio_stream_set_volume, diff --git a/Externals/cubeb/src/cubeb_strings.c b/Externals/cubeb/src/cubeb_strings.c new file mode 100644 index 0000000000..79d7d21b38 --- /dev/null +++ b/Externals/cubeb/src/cubeb_strings.c @@ -0,0 +1,155 @@ +/* + * Copyright © 2011 Mozilla Foundation + * + * This program is made available under an ISC-style license. See the + * accompanying file LICENSE for details. + */ + +#include "cubeb_strings.h" + +#include +#include +#include + +#define CUBEB_STRINGS_INLINE_COUNT 4 + +struct cubeb_strings { + uint32_t size; + uint32_t count; + char ** data; + char * small_store[CUBEB_STRINGS_INLINE_COUNT]; +}; + +int +cubeb_strings_init(cubeb_strings ** strings) +{ + cubeb_strings* strs = NULL; + + if (!strings) { + return CUBEB_ERROR; + } + + strs = calloc(1, sizeof(cubeb_strings)); + assert(strs); + + if (!strs) { + return CUBEB_ERROR; + } + + strs->size = sizeof(strs->small_store) / sizeof(strs->small_store[0]); + strs->count = 0; + strs->data = strs->small_store; + + *strings = strs; + + return CUBEB_OK; +} + +void +cubeb_strings_destroy(cubeb_strings * strings) +{ + char ** sp = NULL; + char ** se = NULL; + + if (!strings) { + return; + } + + sp = strings->data; + se = sp + strings->count; + + for ( ; sp != se; sp++) { + if (*sp) { + free(*sp); + } + } + + if (strings->data != strings->small_store) { + free(strings->data); + } + + free(strings); +} + +/** Look for string in string storage. + @param strings Opaque pointer to interned string storage. + @param s String to look up. + @retval Read-only string or NULL if not found. */ +static char const * +cubeb_strings_lookup(cubeb_strings * strings, char const * s) +{ + char ** sp = NULL; + char ** se = NULL; + + if (!strings || !s) { + return NULL; + } + + sp = strings->data; + se = sp + strings->count; + + for ( ; sp != se; sp++) { + if (*sp && strcmp(*sp, s) == 0) { + return *sp; + } + } + + return NULL; +} + +static char const * +cubeb_strings_push(cubeb_strings * strings, char const * s) +{ + char * is = NULL; + + if (strings->count == strings->size) { + char ** new_data; + uint32_t value_size = sizeof(char const *); + uint32_t new_size = strings->size * 2; + if (!new_size || value_size > (uint32_t)-1 / new_size) { + // overflow + return NULL; + } + + if (strings->small_store == strings->data) { + // First time heap allocation. + new_data = malloc(new_size * value_size); + if (new_data) { + memcpy(new_data, strings->small_store, sizeof(strings->small_store)); + } + } else { + new_data = realloc(strings->data, new_size * value_size); + } + + if (!new_data) { + // out of memory + return NULL; + } + + strings->size = new_size; + strings->data = new_data; + } + + is = strdup(s); + strings->data[strings->count++] = is; + + return is; +} + +char const * +cubeb_strings_intern(cubeb_strings * strings, char const * s) +{ + char const * is = NULL; + + if (!strings || !s) { + return NULL; + } + + is = cubeb_strings_lookup(strings, s); + if (is) { + return is; + } + + return cubeb_strings_push(strings, s); +} + diff --git a/Externals/cubeb/src/cubeb_strings.h b/Externals/cubeb/src/cubeb_strings.h new file mode 100644 index 0000000000..a918a01c5d --- /dev/null +++ b/Externals/cubeb/src/cubeb_strings.h @@ -0,0 +1,44 @@ +/* + * Copyright © 2011 Mozilla Foundation + * + * This program is made available under an ISC-style license. See the + * accompanying file LICENSE for details. + */ + +#ifndef CUBEB_STRINGS_H +#define CUBEB_STRINGS_H + +#include "cubeb/cubeb.h" + +#if defined(__cplusplus) +extern "C" { +#endif + +/** Opaque handle referencing interned string storage. */ +typedef struct cubeb_strings cubeb_strings; + +/** Initialize an interned string structure. + @param strings An out param where an opaque pointer to the + interned string storage will be returned. + @retval CUBEB_OK in case of success. + @retval CUBEB_ERROR in case of error. */ +CUBEB_EXPORT int cubeb_strings_init(cubeb_strings ** strings); + +/** Destroy an interned string structure freeing all associated memory. + @param strings An opaque pointer to the interned string storage to + destroy. */ +CUBEB_EXPORT void cubeb_strings_destroy(cubeb_strings * strings); + +/** Add string to internal storage. + @param strings Opaque pointer to interned string storage. + @param s String to add to storage. + @retval CUBEB_OK + @retval CUBEB_ERROR + */ +CUBEB_EXPORT char const * cubeb_strings_intern(cubeb_strings * strings, char const * s); + +#if defined(__cplusplus) +} +#endif + +#endif // !CUBEB_STRINGS_H diff --git a/Externals/cubeb/src/cubeb_utils.c b/Externals/cubeb/src/cubeb_utils.c deleted file mode 100644 index 6557f08967..0000000000 --- a/Externals/cubeb/src/cubeb_utils.c +++ /dev/null @@ -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. - */ - -#include "cubeb_utils.h" -#include "cubeb_assert.h" - -#include - -static void -device_info_destroy(cubeb_device_info * info) -{ - XASSERT(info); - - free((void *) info->device_id); - free((void *) info->friendly_name); - free((void *) info->group_id); - free((void *) info->vendor_name); -} - -int -cubeb_utils_default_device_collection_destroy(cubeb * context, - cubeb_device_collection * collection) -{ - uint32_t i; - XASSERT(collection); - - (void) context; - - for (i = 0; i < collection->count; i++) - device_info_destroy(&collection->device[i]); - - free(collection->device); - return CUBEB_OK; -} diff --git a/Externals/cubeb/src/cubeb_utils.h b/Externals/cubeb/src/cubeb_utils.h index 00011cec9a..dc08fec992 100644 --- a/Externals/cubeb/src/cubeb_utils.h +++ b/Externals/cubeb/src/cubeb_utils.h @@ -17,7 +17,7 @@ #include #include #include -#if defined(WIN32) +#if defined(_WIN32) #include "cubeb_utils_win.h" #else #include "cubeb_utils_unix.h" @@ -336,17 +336,4 @@ private: using auto_lock = std::lock_guard; #endif // __cplusplus -// C language helpers - -#ifdef __cplusplus -extern "C" { -#endif - -int cubeb_utils_default_device_collection_destroy(cubeb * context, - cubeb_device_collection * collection); - -#ifdef __cplusplus -} -#endif - #endif /* CUBEB_UTILS */ diff --git a/Externals/cubeb/src/cubeb_wasapi.cpp b/Externals/cubeb/src/cubeb_wasapi.cpp index 79ee86ae47..b18662d0a1 100644 --- a/Externals/cubeb/src/cubeb_wasapi.cpp +++ b/Externals/cubeb/src/cubeb_wasapi.cpp @@ -30,6 +30,7 @@ #include "cubeb-internal.h" #include "cubeb_mixer.h" #include "cubeb_resampler.h" +#include "cubeb_strings.h" #include "cubeb_utils.h" #ifndef PKEY_Device_FriendlyName @@ -173,6 +174,7 @@ static std::unique_ptr utf8_to_wstr(char const * str); struct cubeb { cubeb_ops const * ops = &wasapi_ops; + cubeb_strings * device_ids; }; class wasapi_endpoint_notification_client; @@ -382,6 +384,23 @@ private: }; namespace { + +char const * +intern_device_id(cubeb * ctx, wchar_t const * id) +{ + XASSERT(id); + + char const * tmp = wstr_to_utf8(id); + if (!tmp) + return nullptr; + + char const * interned = cubeb_strings_intern(ctx->device_ids, tmp); + + free((void *) tmp); + + return interned; +} + bool has_input(cubeb_stream * stm) { return stm->input_stream_params.rate != 0; @@ -427,32 +446,46 @@ channel_layout_to_mask(cubeb_channel_layout layout) // allocate it in stack, or it will be created and removed repeatedly. // Use static to allocate this local variable in data space instead of stack. static DWORD map[CUBEB_LAYOUT_MAX] = { - 0, // CUBEB_LAYOUT_UNDEFINED - MASK_DUAL_MONO, // CUBEB_LAYOUT_DUAL_MONO - MASK_DUAL_MONO_LFE, // CUBEB_LAYOUT_DUAL_MONO_LFE - MASK_MONO, // CUBEB_LAYOUT_MONO - MASK_MONO_LFE, // CUBEB_LAYOUT_MONO_LFE - MASK_STEREO, // CUBEB_LAYOUT_STEREO - MASK_STEREO_LFE, // CUBEB_LAYOUT_STEREO_LFE - MASK_3F, // CUBEB_LAYOUT_3F - MASK_3F_LFE, // CUBEB_LAYOUT_3F_LFE - MASK_2F1, // CUBEB_LAYOUT_2F1 - MASK_2F1_LFE, // CUBEB_LAYOUT_2F1_LFE - MASK_3F1, // CUBEB_LAYOUT_3F1 - MASK_3F1_LFE, // CUBEB_LAYOUT_3F1_LFE - MASK_2F2, // CUBEB_LAYOUT_2F2 - MASK_2F2_LFE, // CUBEB_LAYOUT_2F2_LFE - MASK_3F2, // CUBEB_LAYOUT_3F2 - MASK_3F2_LFE, // CUBEB_LAYOUT_3F2_LFE - MASK_3F3R_LFE, // CUBEB_LAYOUT_3F3R_LFE - MASK_3F4_LFE, // CUBEB_LAYOUT_3F4_LFE + KSAUDIO_SPEAKER_DIRECTOUT, // CUBEB_LAYOUT_UNDEFINED + MASK_DUAL_MONO, // CUBEB_LAYOUT_DUAL_MONO + MASK_DUAL_MONO_LFE, // CUBEB_LAYOUT_DUAL_MONO_LFE + MASK_MONO, // CUBEB_LAYOUT_MONO + MASK_MONO_LFE, // CUBEB_LAYOUT_MONO_LFE + MASK_STEREO, // CUBEB_LAYOUT_STEREO + MASK_STEREO_LFE, // CUBEB_LAYOUT_STEREO_LFE + MASK_3F, // CUBEB_LAYOUT_3F + MASK_3F_LFE, // CUBEB_LAYOUT_3F_LFE + MASK_2F1, // CUBEB_LAYOUT_2F1 + MASK_2F1_LFE, // CUBEB_LAYOUT_2F1_LFE + MASK_3F1, // CUBEB_LAYOUT_3F1 + MASK_3F1_LFE, // CUBEB_LAYOUT_3F1_LFE + MASK_2F2, // CUBEB_LAYOUT_2F2 + MASK_2F2_LFE, // CUBEB_LAYOUT_2F2_LFE + MASK_3F2, // CUBEB_LAYOUT_3F2 + MASK_3F2_LFE, // CUBEB_LAYOUT_3F2_LFE + MASK_3F3R_LFE, // CUBEB_LAYOUT_3F3R_LFE + MASK_3F4_LFE, // CUBEB_LAYOUT_3F4_LFE }; return map[layout]; } cubeb_channel_layout -mask_to_channel_layout(DWORD mask) +mask_to_channel_layout(WAVEFORMATEX const * fmt) { + DWORD mask = 0; + + if (fmt->wFormatTag == WAVE_FORMAT_EXTENSIBLE) { + WAVEFORMATEXTENSIBLE const * ext = reinterpret_cast(fmt); + mask = ext->dwChannelMask; + } else if (fmt->wFormatTag == WAVE_FORMAT_PCM || + fmt->wFormatTag == WAVE_FORMAT_IEEE_FLOAT) { + if (fmt->nChannels == 1) { + mask = MASK_MONO; + } else if (fmt->nChannels == 2) { + mask = MASK_STEREO; + } + } + switch (mask) { // MASK_DUAL_MONO(_LFE) is same as STEREO(_LFE), so we skip it. case MASK_MONO: return CUBEB_LAYOUT_MONO; @@ -483,27 +516,21 @@ get_rate(cubeb_stream * stm) } uint32_t -hns_to_ms(REFERENCE_TIME hns) +hns_to_frames(uint32_t rate, REFERENCE_TIME hns) { - return static_cast(hns / 10000); + return std::ceil(hns / 10000000.0 * rate); } uint32_t hns_to_frames(cubeb_stream * stm, REFERENCE_TIME hns) { - return hns_to_ms(hns * get_rate(stm)) / 1000; -} - -uint32_t -hns_to_frames(uint32_t rate, REFERENCE_TIME hns) -{ - return hns_to_ms(hns * rate) / 1000; + return hns_to_frames(get_rate(stm), hns); } REFERENCE_TIME frames_to_hns(cubeb_stream * stm, uint32_t frames) { - return frames * 1000 / get_rate(stm); + return std::ceil(frames * 10000000.0 / get_rate(stm)); } /* This returns the size of a frame in the stream, before the eventual upmix @@ -1145,6 +1172,10 @@ int wasapi_init(cubeb ** context, char const * context_name) cubeb * ctx = new cubeb(); ctx->ops = &wasapi_ops; + if (cubeb_strings_init(&ctx->device_ids) != CUBEB_OK) { + free(ctx); + return CUBEB_ERROR; + } *context = ctx; @@ -1212,6 +1243,10 @@ bool stop_and_join_render_thread(cubeb_stream * stm) void wasapi_destroy(cubeb * context) { + if (context->device_ids) { + cubeb_strings_destroy(context->device_ids); + } + delete context; } @@ -1374,8 +1409,7 @@ wasapi_get_preferred_channel_layout(cubeb * context, cubeb_channel_layout * layo } com_heap_ptr mix_format(tmp); - WAVEFORMATEXTENSIBLE * format_pcm = reinterpret_cast(mix_format.get()); - *layout = mask_to_channel_layout(format_pcm->dwChannelMask); + *layout = mask_to_channel_layout(mix_format.get()); LOG("Preferred channel layout: %s", CUBEB_CHANNEL_LAYOUT_MAPS[*layout].name); @@ -1400,8 +1434,6 @@ waveformatex_update_derived_properties(WAVEFORMATEX * format) static void handle_channel_layout(cubeb_stream * stm, EDataFlow direction, com_heap_ptr & mix_format, const cubeb_stream_params * stream_params) { - // The CUBEB_LAYOUT_UNDEFINED can be used for input but it's not allowed for output. - XASSERT(direction == eCapture || stream_params->layout != CUBEB_LAYOUT_UNDEFINED); com_ptr & audio_client = (direction == eRender) ? stm->output_client : stm->input_client; XASSERT(audio_client); /* The docs say that GetMixFormat is always of type WAVEFORMATEXTENSIBLE [1], @@ -1434,6 +1466,7 @@ handle_channel_layout(cubeb_stream * stm, EDataFlow direction, com_heap_ptrnChannels); + XASSERT(closest->wFormatTag == WAVE_FORMAT_EXTENSIBLE); WAVEFORMATEXTENSIBLE * closest_pcm = reinterpret_cast(closest); format_pcm->dwChannelMask = closest_pcm->dwChannelMask; mix_format->nChannels = closest->nChannels; @@ -1442,6 +1475,7 @@ handle_channel_layout(cubeb_stream * stm, EDataFlow direction, com_heap_ptrwFormatTag == WAVE_FORMAT_EXTENSIBLE); *reinterpret_cast(mix_format.get()) = hw_mix_format; } else if (hr == S_OK) { LOG("Requested format accepted by WASAPI."); @@ -1520,21 +1554,23 @@ int setup_wasapi_stream_one_side(cubeb_stream * stm, } com_heap_ptr mix_format(tmp); - WAVEFORMATEXTENSIBLE * format_pcm = reinterpret_cast(mix_format.get()); mix_format->wBitsPerSample = stm->bytes_per_sample * 8; - format_pcm->SubFormat = stm->waveformatextensible_sub_format; + if (mix_format->wFormatTag == WAVE_FORMAT_EXTENSIBLE) { + WAVEFORMATEXTENSIBLE * format_pcm = reinterpret_cast(mix_format.get()); + format_pcm->SubFormat = stm->waveformatextensible_sub_format; + } waveformatex_update_derived_properties(mix_format.get()); /* Set channel layout only when there're more than two channels. Otherwise, * use the default setting retrieved from the stream format of the audio * engine's internal processing by GetMixFormat. */ if (mix_format->nChannels > 2) { - handle_channel_layout(stm, direction ,mix_format, stream_params); + handle_channel_layout(stm, direction, mix_format, stream_params); } mix_params->format = stream_params->format; mix_params->rate = mix_format->nSamplesPerSec; mix_params->channels = mix_format->nChannels; - mix_params->layout = mask_to_channel_layout(format_pcm->dwChannelMask); + mix_params->layout = mask_to_channel_layout(mix_format.get()); if (mix_params->layout == CUBEB_LAYOUT_UNDEFINED) { LOG("Output using undefined layout!\n"); } else if (mix_format->nChannels != CUBEB_CHANNEL_LAYOUT_MAPS[mix_params->layout].channels) { @@ -1769,7 +1805,8 @@ wasapi_stream_init(cubeb * context, cubeb_stream ** stream, stm->output_stream_params = *output_stream_params; stm->output_device = utf8_to_wstr(reinterpret_cast(output_device)); // Make sure the layout matches the channel count. - XASSERT(stm->output_stream_params.channels == CUBEB_CHANNEL_LAYOUT_MAPS[stm->output_stream_params.layout].channels); + XASSERT(stm->output_stream_params.layout == CUBEB_LAYOUT_UNDEFINED || + stm->output_stream_params.channels == CUBEB_CHANNEL_LAYOUT_MAPS[stm->output_stream_params.layout].channels); } switch (output_stream_params ? output_stream_params->format : input_stream_params->format) { @@ -1960,6 +1997,7 @@ int wasapi_stream_start(cubeb_stream * stm) return CUBEB_ERROR; } + 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."); @@ -2013,6 +2051,17 @@ int wasapi_stream_stop(cubeb_stream * stm) return CUBEB_OK; } +int wasapi_stream_reset_default_device(cubeb_stream * stm) +{ + XASSERT(stm && stm->reconfigure_event); + BOOL ok = SetEvent(stm->reconfigure_event); + if (!ok) { + LOG("SetEvent on reconfigure_event failed: %lx", GetLastError()); + return CUBEB_ERROR; + } + return CUBEB_OK; +} + int wasapi_stream_get_position(cubeb_stream * stm, uint64_t * position) { XASSERT(stm && position); @@ -2151,8 +2200,8 @@ wasapi_is_default_device(EDataFlow flow, ERole role, LPCWSTR device_id, return ret; } -static int -wasapi_create_device(cubeb_device_info * ret, IMMDeviceEnumerator * enumerator, IMMDevice * dev) +int +wasapi_create_device(cubeb * ctx, cubeb_device_info& ret, IMMDeviceEnumerator * enumerator, IMMDevice * dev) { com_ptr endpoint; com_ptr devnode; @@ -2181,19 +2230,23 @@ wasapi_create_device(cubeb_device_info * ret, IMMDeviceEnumerator * enumerator, if (FAILED(hr)) return CUBEB_ERROR; com_heap_ptr device_id(tmp); + char const * device_id_intern = intern_device_id(ctx, device_id.get()); + if (!device_id_intern) { + return CUBEB_ERROR; + } + hr = dev->OpenPropertyStore(STGM_READ, propstore.receive()); if (FAILED(hr)) return CUBEB_ERROR; hr = dev->GetState(&state); if (FAILED(hr)) return CUBEB_ERROR; - XASSERT(ret); - ret->device_id = wstr_to_utf8(device_id.get()); - ret->devid = reinterpret_cast(ret->device_id); + ret.device_id = device_id_intern; + ret.devid = reinterpret_cast(ret.device_id); prop_variant namevar; hr = propstore->GetValue(PKEY_Device_FriendlyName, &namevar); if (SUCCEEDED(hr)) - ret->friendly_name = wstr_to_utf8(namevar.pwszVal); + ret.friendly_name = wstr_to_utf8(namevar.pwszVal); devnode = wasapi_get_device_node(enumerator, dev); if (devnode) { @@ -2204,60 +2257,60 @@ wasapi_create_device(cubeb_device_info * ret, IMMDeviceEnumerator * enumerator, prop_variant instancevar; hr = ps->GetValue(PKEY_Device_InstanceId, &instancevar); if (SUCCEEDED(hr)) { - ret->group_id = wstr_to_utf8(instancevar.pwszVal); + ret.group_id = wstr_to_utf8(instancevar.pwszVal); } } - ret->preferred = CUBEB_DEVICE_PREF_NONE; + ret.preferred = CUBEB_DEVICE_PREF_NONE; if (wasapi_is_default_device(flow, eConsole, device_id.get(), enumerator)) - ret->preferred = (cubeb_device_pref)(ret->preferred | CUBEB_DEVICE_PREF_MULTIMEDIA); + ret.preferred = (cubeb_device_pref)(ret.preferred | CUBEB_DEVICE_PREF_MULTIMEDIA); if (wasapi_is_default_device(flow, eCommunications, device_id.get(), enumerator)) - ret->preferred = (cubeb_device_pref)(ret->preferred | CUBEB_DEVICE_PREF_VOICE); + ret.preferred = (cubeb_device_pref)(ret.preferred | CUBEB_DEVICE_PREF_VOICE); if (wasapi_is_default_device(flow, eConsole, device_id.get(), enumerator)) - ret->preferred = (cubeb_device_pref)(ret->preferred | CUBEB_DEVICE_PREF_NOTIFICATION); + ret.preferred = (cubeb_device_pref)(ret.preferred | CUBEB_DEVICE_PREF_NOTIFICATION); - if (flow == eRender) ret->type = CUBEB_DEVICE_TYPE_OUTPUT; - else if (flow == eCapture) ret->type = CUBEB_DEVICE_TYPE_INPUT; + if (flow == eRender) ret.type = CUBEB_DEVICE_TYPE_OUTPUT; + else if (flow == eCapture) ret.type = CUBEB_DEVICE_TYPE_INPUT; switch (state) { case DEVICE_STATE_ACTIVE: - ret->state = CUBEB_DEVICE_STATE_ENABLED; + ret.state = CUBEB_DEVICE_STATE_ENABLED; break; case DEVICE_STATE_UNPLUGGED: - ret->state = CUBEB_DEVICE_STATE_UNPLUGGED; + ret.state = CUBEB_DEVICE_STATE_UNPLUGGED; break; default: - ret->state = CUBEB_DEVICE_STATE_DISABLED; + ret.state = CUBEB_DEVICE_STATE_DISABLED; break; }; - ret->format = static_cast(CUBEB_DEVICE_FMT_F32NE | CUBEB_DEVICE_FMT_S16NE); - ret->default_format = CUBEB_DEVICE_FMT_F32NE; + ret.format = static_cast(CUBEB_DEVICE_FMT_F32NE | CUBEB_DEVICE_FMT_S16NE); + ret.default_format = CUBEB_DEVICE_FMT_F32NE; prop_variant fmtvar; hr = propstore->GetValue(PKEY_AudioEngine_DeviceFormat, &fmtvar); if (SUCCEEDED(hr) && fmtvar.vt == VT_BLOB) { if (fmtvar.blob.cbSize == sizeof(PCMWAVEFORMAT)) { const PCMWAVEFORMAT * pcm = reinterpret_cast(fmtvar.blob.pBlobData); - ret->max_rate = ret->min_rate = ret->default_rate = pcm->wf.nSamplesPerSec; - ret->max_channels = pcm->wf.nChannels; + ret.max_rate = ret.min_rate = ret.default_rate = pcm->wf.nSamplesPerSec; + ret.max_channels = pcm->wf.nChannels; } else if (fmtvar.blob.cbSize >= sizeof(WAVEFORMATEX)) { WAVEFORMATEX* wfx = reinterpret_cast(fmtvar.blob.pBlobData); if (fmtvar.blob.cbSize >= sizeof(WAVEFORMATEX) + wfx->cbSize || wfx->wFormatTag == WAVE_FORMAT_PCM) { - ret->max_rate = ret->min_rate = ret->default_rate = wfx->nSamplesPerSec; - ret->max_channels = wfx->nChannels; + ret.max_rate = ret.min_rate = ret.default_rate = wfx->nSamplesPerSec; + ret.max_channels = wfx->nChannels; } } } if (SUCCEEDED(dev->Activate(__uuidof(IAudioClient), CLSCTX_INPROC_SERVER, NULL, client.receive_vpp())) && SUCCEEDED(client->GetDevicePeriod(&def_period, &min_period))) { - ret->latency_lo = hns_to_frames(ret->default_rate, min_period); - ret->latency_hi = hns_to_frames(ret->default_rate, def_period); + ret.latency_lo = hns_to_frames(ret.default_rate, min_period); + ret.latency_hi = hns_to_frames(ret.default_rate, def_period); } else { - ret->latency_lo = 0; - ret->latency_hi = 0; + ret.latency_lo = 0; + ret.latency_hi = 0; } return CUBEB_OK; @@ -2300,11 +2353,11 @@ wasapi_enumerate_devices(cubeb * context, cubeb_device_type type, LOG("IMMDeviceCollection::GetCount() failed: %lx", hr); return CUBEB_ERROR; } - cubeb_device_info * devices = - (cubeb_device_info *) calloc(cc, sizeof(cubeb_device_info)); - if (!devices) { + cubeb_device_info * devices = new cubeb_device_info[cc]; + if (!devices) return CUBEB_ERROR; - } + + PodZero(devices, cc); out->count = 0; for (i = 0; i < cc; i++) { com_ptr dev; @@ -2313,8 +2366,8 @@ wasapi_enumerate_devices(cubeb * context, cubeb_device_type type, LOG("IMMDeviceCollection::Item(%u) failed: %lx", i-1, hr); continue; } - auto cur = &devices[out->count]; - if (wasapi_create_device(cur, enumerator.get(), dev.get()) == CUBEB_OK) { + if (wasapi_create_device(context, devices[out->count], + enumerator.get(), dev.get()) == CUBEB_OK) { out->count += 1; } } @@ -2323,6 +2376,21 @@ wasapi_enumerate_devices(cubeb * context, cubeb_device_type type, return CUBEB_OK; } +static int +wasapi_device_collection_destroy(cubeb * /*ctx*/, cubeb_device_collection * collection) +{ + XASSERT(collection); + + for (size_t n = 0; n < collection->count; n++) { + cubeb_device_info& dev = collection->device[n]; + delete [] dev.friendly_name; + delete [] dev.group_id; + } + + delete [] collection->device; + return CUBEB_OK; +} + cubeb_ops const wasapi_ops = { /*.init =*/ wasapi_init, /*.get_backend_id =*/ wasapi_get_backend_id, @@ -2331,12 +2399,13 @@ cubeb_ops const wasapi_ops = { /*.get_preferred_sample_rate =*/ wasapi_get_preferred_sample_rate, /*.get_preferred_channel_layout =*/ wasapi_get_preferred_channel_layout, /*.enumerate_devices =*/ wasapi_enumerate_devices, - /*.device_collection_destroy =*/ cubeb_utils_default_device_collection_destroy, + /*.device_collection_destroy =*/ wasapi_device_collection_destroy, /*.destroy =*/ wasapi_destroy, /*.stream_init =*/ wasapi_stream_init, /*.stream_destroy =*/ wasapi_stream_destroy, /*.stream_start =*/ wasapi_stream_start, /*.stream_stop =*/ wasapi_stream_stop, + /*.stream_reset_default_device =*/ wasapi_stream_reset_default_device, /*.stream_get_position =*/ wasapi_stream_get_position, /*.stream_get_latency =*/ wasapi_stream_get_latency, /*.stream_set_volume =*/ wasapi_stream_set_volume, diff --git a/Externals/cubeb/src/cubeb_winmm.c b/Externals/cubeb/src/cubeb_winmm.c index d0aab08173..ce7371a433 100644 --- a/Externals/cubeb/src/cubeb_winmm.c +++ b/Externals/cubeb/src/cubeb_winmm.c @@ -19,7 +19,6 @@ #include #include "cubeb/cubeb.h" #include "cubeb-internal.h" -#include "cubeb_utils.h" /* This is missing from the MinGW headers. Use a safe fallback. */ #if !defined(MEMORY_ALLOCATION_ALIGNMENT) @@ -1017,6 +1016,26 @@ winmm_enumerate_devices(cubeb * context, cubeb_device_type type, return CUBEB_OK; } +static int +winmm_device_collection_destroy(cubeb * ctx, + cubeb_device_collection * collection) +{ + uint32_t i; + XASSERT(collection); + + (void) ctx; + + for (i = 0; i < collection->count; i++) { + free((void *) collection->device[i].device_id); + free((void *) collection->device[i].friendly_name); + free((void *) collection->device[i].group_id); + free((void *) collection->device[i].vendor_name); + } + + free(collection->device); + return CUBEB_OK; +} + static struct cubeb_ops const winmm_ops = { /*.init =*/ winmm_init, /*.get_backend_id =*/ winmm_get_backend_id, @@ -1025,12 +1044,13 @@ static struct cubeb_ops const winmm_ops = { /*.get_preferred_sample_rate =*/ winmm_get_preferred_sample_rate, /*.get_preferred_channel_layout =*/ NULL, /*.enumerate_devices =*/ winmm_enumerate_devices, - /*.device_collection_destroy =*/ cubeb_utils_default_device_collection_destroy, + /*.device_collection_destroy =*/ winmm_device_collection_destroy, /*.destroy =*/ winmm_destroy, /*.stream_init =*/ winmm_stream_init, /*.stream_destroy =*/ winmm_stream_destroy, /*.stream_start =*/ winmm_stream_start, /*.stream_stop =*/ winmm_stream_stop, + /*.stream_reset_default_device =*/ NULL, /*.stream_get_position =*/ winmm_stream_get_position, /*.stream_get_latency = */ winmm_stream_get_latency, /*.stream_set_volume =*/ winmm_stream_set_volume, diff --git a/Externals/cubeb/src/speex/resample.c b/Externals/cubeb/src/speex/resample.c index aadf68517e..10cb06593d 100644 --- a/Externals/cubeb/src/speex/resample.c +++ b/Externals/cubeb/src/speex/resample.c @@ -798,11 +798,10 @@ EXPORT SpeexResamplerState *speex_resampler_init(spx_uint32_t nb_channels, spx_u EXPORT SpeexResamplerState *speex_resampler_init_frac(spx_uint32_t nb_channels, spx_uint32_t ratio_num, spx_uint32_t ratio_den, spx_uint32_t in_rate, spx_uint32_t out_rate, int quality, int *err) { - spx_uint32_t i; SpeexResamplerState *st; int filter_err; - if (quality > 10 || quality < 0) + if (nb_channels == 0 || ratio_num == 0 || ratio_den == 0 || quality > 10 || quality < 0) { if (err) *err = RESAMPLER_ERR_INVALID_ARG; @@ -1111,6 +1110,10 @@ EXPORT int speex_resampler_set_rate_frac(SpeexResamplerState *st, spx_uint32_t r spx_uint32_t fact; spx_uint32_t old_den; spx_uint32_t i; + + if (ratio_num == 0 || ratio_den == 0) + return RESAMPLER_ERR_INVALID_ARG; + if (st->in_rate == in_rate && st->out_rate == out_rate && st->num_rate == ratio_num && st->den_rate == ratio_den) return RESAMPLER_ERR_SUCCESS; diff --git a/Source/Core/AudioCommon/CubebStream.cpp b/Source/Core/AudioCommon/CubebStream.cpp index 272060cd74..a12f0fda4b 100644 --- a/Source/Core/AudioCommon/CubebStream.cpp +++ b/Source/Core/AudioCommon/CubebStream.cpp @@ -56,7 +56,7 @@ bool CubebStream::Start() } u32 minimum_latency = 0; - if (cubeb_get_min_latency(m_ctx.get(), params, &minimum_latency) != CUBEB_OK) + if (cubeb_get_min_latency(m_ctx.get(), ¶ms, &minimum_latency) != CUBEB_OK) ERROR_LOG(AUDIO, "Error getting minimum latency"); INFO_LOG(AUDIO, "Minimum latency: %i frames", minimum_latency); diff --git a/Source/Core/Core/HW/EXI/EXI_DeviceMic.cpp b/Source/Core/Core/HW/EXI/EXI_DeviceMic.cpp index 4f7540c971..72b99c0842 100644 --- a/Source/Core/Core/HW/EXI/EXI_DeviceMic.cpp +++ b/Source/Core/Core/HW/EXI/EXI_DeviceMic.cpp @@ -80,7 +80,7 @@ void CEXIMic::StreamStart() params.layout = CUBEB_LAYOUT_MONO; u32 minimum_latency; - if (cubeb_get_min_latency(m_cubeb_ctx.get(), params, &minimum_latency) != CUBEB_OK) + if (cubeb_get_min_latency(m_cubeb_ctx.get(), ¶ms, &minimum_latency) != CUBEB_OK) { WARN_LOG(EXPANSIONINTERFACE, "Error getting minimum latency"); }