early-access version 2820

This commit is contained in:
pineappleEA
2022-07-07 09:40:51 +02:00
parent 1d9d4902d8
commit 0530d98957
243 changed files with 32420 additions and 1084 deletions

View File

@@ -139,7 +139,7 @@ if(NOT BUNDLE_SPEEX)
endif()
if(NOT TARGET speex)
add_library(speex STATIC subprojects/speex/resample.c)
add_library(speex OBJECT subprojects/speex/resample.c)
set_target_properties(speex PROPERTIES POSITION_INDEPENDENT_CODE TRUE)
target_include_directories(speex INTERFACE subprojects)
target_compile_definitions(speex PUBLIC
@@ -259,7 +259,7 @@ if(USE_WASAPI)
target_sources(cubeb PRIVATE
src/cubeb_wasapi.cpp)
target_compile_definitions(cubeb PRIVATE USE_WASAPI)
target_link_libraries(cubeb PRIVATE avrt ole32)
target_link_libraries(cubeb PRIVATE avrt ole32 ksuser)
endif()
check_include_files("windows.h;mmsystem.h" USE_WINMM)
@@ -284,9 +284,22 @@ if(HAVE_SYS_SOUNDCARD_H)
try_compile(USE_OSS "${PROJECT_BINARY_DIR}/compile_tests"
${PROJECT_SOURCE_DIR}/cmake/compile_tests/oss_is_v4.c)
if(USE_OSS)
target_sources(cubeb PRIVATE
src/cubeb_oss.c)
target_compile_definitions(cubeb PRIVATE USE_OSS)
# strlcpy is not available on BSD systems that use glibc,
# like Debian kfreebsd, so try using libbsd if available
include(CheckSymbolExists)
check_symbol_exists(strlcpy string.h HAVE_STRLCPY)
if(NOT HAVE_STRLCPY)
pkg_check_modules(libbsd-overlay IMPORTED_TARGET libbsd-overlay)
if(libbsd-overlay_FOUND)
target_link_libraries(cubeb PRIVATE PkgConfig::libbsd-overlay)
set(HAVE_STRLCPY true)
endif()
endif()
if (HAVE_STRLCPY)
target_sources(cubeb PRIVATE
src/cubeb_oss.c)
target_compile_definitions(cubeb PRIVATE USE_OSS)
endif()
endif()
endif()
@@ -367,7 +380,7 @@ if(BUILD_TESTS)
add_executable(test_${NAME} test/test_${NAME}.cpp)
target_include_directories(test_${NAME} PRIVATE ${gtest_SOURCE_DIR}/include src)
target_link_libraries(test_${NAME} PRIVATE cubeb gtest_main)
add_test(${NAME} test_${NAME})
add_test(${NAME} test_${NAME} --gtest_death_test_style=threadsafe)
add_sanitizers(test_${NAME})
install(TARGETS test_${NAME})
endmacro(cubeb_add_test)

View File

@@ -2,7 +2,7 @@
You must have CMake v3.1 or later installed.
1. `git clone --recursive https://github.com/kinetiknz/cubeb.git`
1. `git clone --recursive https://github.com/mozilla/cubeb.git`
2. `mkdir cubeb-build`
3. `cd cubeb-build`
3. `cmake ../cubeb`

View File

@@ -935,7 +935,8 @@ aaudio_stream_init_impl(cubeb_stream * stm, cubeb_devid input_device,
stm->resampler = cubeb_resampler_create(
stm, input_stream_params ? &in_params : NULL,
output_stream_params ? &out_params : NULL, target_sample_rate,
stm->data_callback, stm->user_ptr, CUBEB_RESAMPLER_QUALITY_DEFAULT);
stm->data_callback, stm->user_ptr, CUBEB_RESAMPLER_QUALITY_DEFAULT,
CUBEB_RESAMPLER_RECLOCK_NONE);
if (!stm->resampler) {
LOG("Failed to create resampler");

View File

@@ -1442,6 +1442,13 @@ audiounit_destroy(cubeb * ctx)
audiounit_active_streams(ctx));
}
// Destroying a cubeb context with device collection callbacks registered
// is misuse of the API, assert then attempt to clean up.
assert(!ctx->input_collection_changed_callback &&
!ctx->input_collection_changed_user_ptr &&
!ctx->output_collection_changed_callback &&
!ctx->output_collection_changed_user_ptr);
/* Unregister the callback if necessary. */
if (ctx->input_collection_changed_callback) {
audiounit_remove_device_listener(ctx, CUBEB_DEVICE_TYPE_INPUT);
@@ -2700,7 +2707,8 @@ audiounit_setup_stream(cubeb_stream * stm)
stm->resampler.reset(cubeb_resampler_create(
stm, has_input(stm) ? &input_unconverted_params : NULL,
has_output(stm) ? &stm->output_stream_params : NULL, target_sample_rate,
stm->data_callback, stm->user_ptr, CUBEB_RESAMPLER_QUALITY_DESKTOP));
stm->data_callback, stm->user_ptr, CUBEB_RESAMPLER_QUALITY_DESKTOP,
CUBEB_RESAMPLER_RECLOCK_NONE));
if (!stm->resampler) {
LOG("(%p) Could not create resampler.", stm);
return CUBEB_ERROR;

View File

@@ -925,15 +925,18 @@ cbjack_stream_init(cubeb * context, cubeb_stream ** stream,
if (stm->devs == DUPLEX) {
stm->resampler = cubeb_resampler_create(
stm, &stm->in_params, &stm->out_params, stream_actual_rate,
stm->data_callback, stm->user_ptr, CUBEB_RESAMPLER_QUALITY_DESKTOP);
stm->data_callback, stm->user_ptr, CUBEB_RESAMPLER_QUALITY_DESKTOP,
CUBEB_RESAMPLER_RECLOCK_NONE);
} else if (stm->devs == IN_ONLY) {
stm->resampler = cubeb_resampler_create(
stm, &stm->in_params, nullptr, stream_actual_rate, stm->data_callback,
stm->user_ptr, CUBEB_RESAMPLER_QUALITY_DESKTOP);
stm->user_ptr, CUBEB_RESAMPLER_QUALITY_DESKTOP,
CUBEB_RESAMPLER_RECLOCK_NONE);
} else if (stm->devs == OUT_ONLY) {
stm->resampler = cubeb_resampler_create(
stm, nullptr, &stm->out_params, stream_actual_rate, stm->data_callback,
stm->user_ptr, CUBEB_RESAMPLER_QUALITY_DESKTOP);
stm->user_ptr, CUBEB_RESAMPLER_QUALITY_DESKTOP,
CUBEB_RESAMPLER_RECLOCK_NONE);
}
if (!stm->resampler) {

View File

@@ -1479,7 +1479,8 @@ opensl_stream_init(cubeb * ctx, cubeb_stream ** stream,
stm->resampler = cubeb_resampler_create(
stm, input_stream_params ? &input_params : NULL,
output_stream_params ? &output_params : NULL, target_sample_rate,
data_callback, user_ptr, CUBEB_RESAMPLER_QUALITY_DEFAULT);
data_callback, user_ptr, CUBEB_RESAMPLER_QUALITY_DEFAULT,
CUBEB_RESAMPLER_RECLOCK_NONE);
if (!stm->resampler) {
LOG("Failed to create resampler");
opensl_stream_destroy(stm);

View File

@@ -96,9 +96,8 @@ struct oss_stream {
oss_devnode_t name;
int fd;
void * buf;
unsigned int nfr; /* Number of frames allocated */
unsigned int nfrags;
unsigned int bufframes;
unsigned int maxframes;
struct stream_info {
int channels;
@@ -824,9 +823,9 @@ retry:
pfds[0].fd = s->play.fd;
pfds[1].fd = -1;
goto retry;
} else if (tnfr > (long)s->play.bufframes) {
} else if (tnfr > (long)s->play.maxframes) {
/* too many frames available - limit */
tnfr = (long)s->play.bufframes;
tnfr = (long)s->play.maxframes;
}
if (nfr > tnfr) {
nfr = tnfr;
@@ -842,9 +841,9 @@ retry:
pfds[0].fd = -1;
pfds[1].fd = s->record.fd;
goto retry;
} else if (tnfr > (long)s->record.bufframes) {
} else if (tnfr > (long)s->record.maxframes) {
/* too many frames available - limit */
tnfr = (long)s->record.bufframes;
tnfr = (long)s->record.maxframes;
}
if (nfr > tnfr) {
nfr = tnfr;
@@ -1061,6 +1060,8 @@ oss_stream_init(cubeb * context, cubeb_stream ** stream,
}
if (input_stream_params != NULL) {
unsigned int nb_channels;
uint32_t minframes;
if (input_stream_params->prefs & CUBEB_STREAM_PREF_LOOPBACK) {
LOG("Loopback not supported");
ret = CUBEB_ERROR_NOT_SUPPORTED;
@@ -1089,18 +1090,17 @@ oss_stream_init(cubeb * context, cubeb_stream ** stream,
(input_stream_params->format == CUBEB_SAMPLE_FLOAT32NE);
s->record.frame_size =
s->record.info.channels * (s->record.info.precision / 8);
s->record.nfrags = OSS_NFRAGS;
s->record.nfr = latency_frames / OSS_NFRAGS;
s->record.bufframes = s->record.nfrags * s->record.nfr;
uint32_t minnfr;
oss_get_min_latency(context, *input_stream_params, &minnfr);
if (s->record.nfr < minnfr) {
s->record.nfr = minnfr;
s->record.nfrags = latency_frames / minnfr;
s->record.bufframes = latency_frames;
oss_get_min_latency(context, *input_stream_params, &minframes);
if (s->record.bufframes < minframes) {
s->record.bufframes = minframes;
}
}
if (output_stream_params != NULL) {
unsigned int nb_channels;
uint32_t minframes;
if (output_stream_params->prefs & CUBEB_STREAM_PREF_LOOPBACK) {
LOG("Loopback not supported");
ret = CUBEB_ERROR_NOT_SUPPORTED;
@@ -1128,19 +1128,16 @@ oss_stream_init(cubeb * context, cubeb_stream ** stream,
}
s->play.floating = (output_stream_params->format == CUBEB_SAMPLE_FLOAT32NE);
s->play.frame_size = s->play.info.channels * (s->play.info.precision / 8);
s->play.nfrags = OSS_NFRAGS;
s->play.nfr = latency_frames / OSS_NFRAGS;
uint32_t minnfr;
oss_get_min_latency(context, *output_stream_params, &minnfr);
if (s->play.nfr < minnfr) {
s->play.nfr = minnfr;
s->play.nfrags = latency_frames / minnfr;
s->play.bufframes = latency_frames;
oss_get_min_latency(context, *output_stream_params, &minframes);
if (s->play.bufframes < minframes) {
s->play.bufframes = minframes;
}
s->play.bufframes = s->play.nfrags * s->play.nfr;
}
if (s->play.fd != -1) {
int frag = oss_get_frag_params(
oss_calc_frag_shift(s->play.nfr, s->play.frame_size));
oss_calc_frag_shift(s->play.bufframes, s->play.frame_size));
if (ioctl(s->play.fd, SNDCTL_DSP_SETFRAGMENT, &frag))
LOG("Failed to set play fd with SNDCTL_DSP_SETFRAGMENT. frag: 0x%x",
frag);
@@ -1148,19 +1145,28 @@ oss_stream_init(cubeb * context, cubeb_stream ** stream,
if (ioctl(s->play.fd, SNDCTL_DSP_GETOSPACE, &bi))
LOG("Failed to get play fd's buffer info.");
else {
s->play.nfr = bi.fragsize / s->play.frame_size;
s->play.nfrags = bi.fragments;
s->play.bufframes = s->play.nfr * s->play.nfrags;
s->play.bufframes = (bi.fragsize * bi.fragstotal) / s->play.frame_size;
}
int lw;
int lw = s->play.frame_size;
/*
* Force 32 ms service intervals at most, or when recording is
* active, use the recording service intervals as a reference.
*/
s->play.maxframes = (32 * output_stream_params->rate) / 1000;
if (s->record.fd != -1 || s->play.maxframes >= s->play.bufframes) {
lw = s->play.frame_size; /* Feed data when possible. */
s->play.maxframes = s->play.bufframes;
} else {
lw = (s->play.bufframes - s->play.maxframes) * s->play.frame_size;
}
if (ioctl(s->play.fd, SNDCTL_DSP_LOW_WATER, &lw))
LOG("Audio device \"%s\" (play) could not set trigger threshold",
s->play.name);
}
if (s->record.fd != -1) {
int frag = oss_get_frag_params(
oss_calc_frag_shift(s->record.nfr, s->record.frame_size));
oss_calc_frag_shift(s->record.bufframes, s->record.frame_size));
if (ioctl(s->record.fd, SNDCTL_DSP_SETFRAGMENT, &frag))
LOG("Failed to set record fd with SNDCTL_DSP_SETFRAGMENT. frag: 0x%x",
frag);
@@ -1168,11 +1174,11 @@ oss_stream_init(cubeb * context, cubeb_stream ** stream,
if (ioctl(s->record.fd, SNDCTL_DSP_GETISPACE, &bi))
LOG("Failed to get record fd's buffer info.");
else {
s->record.nfr = bi.fragsize / s->record.frame_size;
s->record.nfrags = bi.fragments;
s->record.bufframes = s->record.nfr * s->record.nfrags;
s->record.bufframes =
(bi.fragsize * bi.fragstotal) / s->record.frame_size;
}
s->record.maxframes = s->record.bufframes;
int lw = s->record.frame_size;
if (ioctl(s->record.fd, SNDCTL_DSP_LOW_WATER, &lw))
LOG("Audio device \"%s\" (record) could not set trigger threshold",

View File

@@ -783,6 +783,10 @@ pulse_context_destroy(cubeb * ctx)
static void
pulse_destroy(cubeb * ctx)
{
assert(!ctx->input_collection_changed_callback &&
!ctx->input_collection_changed_user_ptr &&
!ctx->output_collection_changed_callback &&
!ctx->output_collection_changed_user_ptr);
free(ctx->context_name);
if (ctx->context) {
pulse_context_destroy(ctx);

View File

@@ -323,7 +323,8 @@ cubeb_resampler_create(cubeb_stream * stream,
cubeb_stream_params * input_params,
cubeb_stream_params * output_params,
unsigned int target_rate, cubeb_data_callback callback,
void * user_ptr, cubeb_resampler_quality quality)
void * user_ptr, cubeb_resampler_quality quality,
cubeb_resampler_reclock reclock)
{
cubeb_sample_format format;
@@ -337,13 +338,13 @@ cubeb_resampler_create(cubeb_stream * stream,
switch (format) {
case CUBEB_SAMPLE_S16NE:
return cubeb_resampler_create_internal<short>(stream, input_params,
output_params, target_rate,
callback, user_ptr, quality);
return cubeb_resampler_create_internal<short>(
stream, input_params, output_params, target_rate, callback, user_ptr,
quality, reclock);
case CUBEB_SAMPLE_FLOAT32NE:
return cubeb_resampler_create_internal<float>(stream, input_params,
output_params, target_rate,
callback, user_ptr, quality);
return cubeb_resampler_create_internal<float>(
stream, input_params, output_params, target_rate, callback, user_ptr,
quality, reclock);
default:
assert(false);
return nullptr;

View File

@@ -21,6 +21,11 @@ typedef enum {
CUBEB_RESAMPLER_QUALITY_DESKTOP
} cubeb_resampler_quality;
typedef enum {
CUBEB_RESAMPLER_RECLOCK_NONE,
CUBEB_RESAMPLER_RECLOCK_INPUT
} cubeb_resampler_reclock;
/**
* Create a resampler to adapt the requested sample rate into something that
* is accepted by the audio backend.
@@ -44,7 +49,8 @@ cubeb_resampler_create(cubeb_stream * stream,
cubeb_stream_params * input_params,
cubeb_stream_params * output_params,
unsigned int target_rate, cubeb_data_callback callback,
void * user_ptr, cubeb_resampler_quality quality);
void * user_ptr, cubeb_resampler_quality quality,
cubeb_resampler_reclock reclock);
/**
* Fill the buffer with frames acquired using the data callback. Resampling will

View File

@@ -496,7 +496,8 @@ cubeb_resampler_create_internal(cubeb_stream * stream,
cubeb_stream_params * output_params,
unsigned int target_rate,
cubeb_data_callback callback, void * user_ptr,
cubeb_resampler_quality quality)
cubeb_resampler_quality quality,
cubeb_resampler_reclock reclock)
{
std::unique_ptr<cubeb_resampler_speex_one_way<T>> input_resampler = nullptr;
std::unique_ptr<cubeb_resampler_speex_one_way<T>> output_resampler = nullptr;

View File

@@ -11,24 +11,23 @@
#include "cubeb-internal.h"
#include <windows.h>
/* This wraps a critical section to track the owner in debug mode, adapted from
/* This wraps an SRWLock to track the owner in debug mode, adapted from
NSPR and http://blogs.msdn.com/b/oldnewthing/archive/2013/07/12/10433554.aspx
*/
class owned_critical_section {
public:
owned_critical_section()
: srwlock(SRWLOCK_INIT)
#ifndef NDEBUG
: owner(0)
,
owner(0)
#endif
{
InitializeCriticalSection(&critical_section);
}
~owned_critical_section() { DeleteCriticalSection(&critical_section); }
void lock()
{
EnterCriticalSection(&critical_section);
AcquireSRWLockExclusive(&srwlock);
#ifndef NDEBUG
XASSERT(owner != GetCurrentThreadId() && "recursive locking");
owner = GetCurrentThreadId();
@@ -41,7 +40,7 @@ public:
/* GetCurrentThreadId cannot return 0: it is not a the valid thread id */
owner = 0;
#endif
LeaveCriticalSection(&critical_section);
ReleaseSRWLockExclusive(&srwlock);
}
/* This is guaranteed to have the good behaviour if it succeeds. The behaviour
@@ -55,12 +54,12 @@ public:
}
private:
CRITICAL_SECTION critical_section;
SRWLOCK srwlock;
#ifndef NDEBUG
DWORD owner;
#endif
// Disallow copy and assignment because CRICICAL_SECTION cannot be copied.
// Disallow copy and assignment because SRWLock cannot be copied.
owned_critical_section(const owned_critical_section &);
owned_critical_section & operator=(const owned_critical_section &);
};

View File

@@ -183,6 +183,46 @@ private:
extern cubeb_ops const wasapi_ops;
static com_heap_ptr<wchar_t>
wasapi_get_default_device_id(EDataFlow flow, ERole role,
IMMDeviceEnumerator * enumerator);
struct wasapi_default_devices {
wasapi_default_devices(IMMDeviceEnumerator * enumerator)
: render_console_id(
wasapi_get_default_device_id(eRender, eConsole, enumerator)),
render_comms_id(
wasapi_get_default_device_id(eRender, eCommunications, enumerator)),
capture_console_id(
wasapi_get_default_device_id(eCapture, eConsole, enumerator)),
capture_comms_id(
wasapi_get_default_device_id(eCapture, eCommunications, enumerator))
{
}
bool is_default(EDataFlow flow, ERole role, wchar_t const * id)
{
wchar_t const * default_id = nullptr;
if (flow == eRender && role == eConsole) {
default_id = this->render_console_id.get();
} else if (flow == eRender && role == eCommunications) {
default_id = this->render_comms_id.get();
} else if (flow == eCapture && role == eConsole) {
default_id = this->capture_console_id.get();
} else if (flow == eCapture && role == eCommunications) {
default_id = this->capture_comms_id.get();
}
return default_id && wcscmp(id, default_id) == 0;
}
private:
com_heap_ptr<wchar_t> render_console_id;
com_heap_ptr<wchar_t> render_comms_id;
com_heap_ptr<wchar_t> capture_console_id;
com_heap_ptr<wchar_t> capture_comms_id;
};
int
wasapi_stream_stop(cubeb_stream * stm);
int
@@ -195,7 +235,8 @@ ERole
pref_to_role(cubeb_stream_prefs param);
int
wasapi_create_device(cubeb * ctx, cubeb_device_info & ret,
IMMDeviceEnumerator * enumerator, IMMDevice * dev);
IMMDeviceEnumerator * enumerator, IMMDevice * dev,
wasapi_default_devices * defaults);
void
wasapi_destroy_device(cubeb_device_info * device_info);
static int
@@ -216,6 +257,7 @@ class monitor_device_notifications;
struct cubeb {
cubeb_ops const * ops = &wasapi_ops;
owned_critical_section lock;
cubeb_strings * device_ids;
/* Device enumerator to get notifications when the
device collection change. */
@@ -716,6 +758,8 @@ intern_device_id(cubeb * ctx, wchar_t const * id)
{
XASSERT(id);
auto_lock lock(ctx->lock);
char const * tmp = wstr_to_utf8(id);
if (!tmp) {
return nullptr;
@@ -902,16 +946,16 @@ refill(cubeb_stream * stm, void * input_buffer, long input_frames_count,
return out_frames;
}
int
bool
trigger_async_reconfigure(cubeb_stream * stm)
{
XASSERT(stm && stm->reconfigure_event);
LOG("Try reconfiguring the stream");
BOOL ok = SetEvent(stm->reconfigure_event);
if (!ok) {
LOG("SetEvent on reconfigure_event failed: %lx", GetLastError());
return CUBEB_ERROR;
}
return CUBEB_OK;
return static_cast<bool>(ok);
}
/* This helper grabs all the frames available from a capture client, put them in
@@ -940,8 +984,16 @@ get_input_buffer(cubeb_stream * stm)
if (hr == AUDCLNT_E_DEVICE_INVALIDATED) {
// Application can recover from this error. More info
// https://msdn.microsoft.com/en-us/library/windows/desktop/dd316605(v=vs.85).aspx
LOG("Device invalidated error, reset default device");
trigger_async_reconfigure(stm);
LOG("Input device invalidated error");
// No need to reset device if user asks to use particular device, or
// switching is disabled.
if (stm->input_device_id ||
(stm->input_stream_params.prefs &
CUBEB_STREAM_PREF_DISABLE_DEVICE_SWITCHING) ||
!trigger_async_reconfigure(stm)) {
stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR);
return false;
}
return true;
}
@@ -1046,8 +1098,16 @@ get_output_buffer(cubeb_stream * stm, void *& buffer, size_t & frame_count)
if (hr == AUDCLNT_E_DEVICE_INVALIDATED) {
// Application can recover from this error. More info
// https://msdn.microsoft.com/en-us/library/windows/desktop/dd316605(v=vs.85).aspx
LOG("Device invalidated error, reset default device");
trigger_async_reconfigure(stm);
LOG("Output device invalidated error");
// No need to reset device if user asks to use particular device, or
// switching is disabled.
if (stm->output_device_id ||
(stm->output_stream_params.prefs &
CUBEB_STREAM_PREF_DISABLE_DEVICE_SWITCHING) ||
!trigger_async_reconfigure(stm)) {
stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR);
return false;
}
return true;
}
@@ -1397,7 +1457,7 @@ wasapi_destroy(cubeb * context);
HRESULT
register_notification_client(cubeb_stream * stm)
{
XASSERT(stm->device_enumerator);
XASSERT(stm->device_enumerator && !stm->notification_client);
stm->notification_client.reset(new wasapi_endpoint_notification_client(
stm->reconfigure_event, stm->role));
@@ -1415,7 +1475,7 @@ register_notification_client(cubeb_stream * stm)
HRESULT
unregister_notification_client(cubeb_stream * stm)
{
XASSERT(stm->device_enumerator);
XASSERT(stm->device_enumerator && stm->notification_client);
HRESULT hr = stm->device_enumerator->UnregisterEndpointNotificationCallback(
stm->notification_client.get());
@@ -1454,6 +1514,9 @@ get_endpoint(com_ptr<IMMDevice> & device, LPCWSTR devid)
HRESULT
register_collection_notification_client(cubeb * context)
{
context->lock.assert_current_thread_owns();
XASSERT(!context->device_collection_enumerator &&
!context->collection_notification_client);
HRESULT hr = CoCreateInstance(
__uuidof(MMDeviceEnumerator), NULL, CLSCTX_INPROC_SERVER,
IID_PPV_ARGS(context->device_collection_enumerator.receive()));
@@ -1480,6 +1543,9 @@ register_collection_notification_client(cubeb * context)
HRESULT
unregister_collection_notification_client(cubeb * context)
{
context->lock.assert_current_thread_owns();
XASSERT(context->device_collection_enumerator &&
context->collection_notification_client);
HRESULT hr = context->device_collection_enumerator
->UnregisterEndpointNotificationCallback(
context->collection_notification_client.get());
@@ -1608,6 +1674,7 @@ wasapi_init(cubeb ** context, char const * context_name)
cubeb * ctx = new cubeb();
ctx->ops = &wasapi_ops;
auto_lock lock(ctx->lock);
if (cubeb_strings_init(&ctx->device_ids) != CUBEB_OK) {
delete ctx;
return CUBEB_ERROR;
@@ -1682,6 +1749,10 @@ stop_and_join_render_thread(cubeb_stream * stm)
void
wasapi_destroy(cubeb * context)
{
auto_lock lock(context->lock);
XASSERT(!context->device_collection_enumerator &&
!context->collection_notification_client);
if (context->device_ids) {
cubeb_strings_destroy(context->device_ids);
}
@@ -1887,7 +1958,7 @@ handle_channel_layout(cubeb_stream * stm, EDataFlow direction,
}
}
static bool
static int
initialize_iaudioclient2(com_ptr<IAudioClient> & audio_client)
{
com_ptr<IAudioClient2> audio_client2;
@@ -1910,8 +1981,8 @@ initialize_iaudioclient2(com_ptr<IAudioClient> & audio_client)
return CUBEB_OK;
}
// Not static to suppress a warning.
/* static */ bool
#if 0
bool
initialize_iaudioclient3(com_ptr<IAudioClient> & audio_client,
cubeb_stream * stm,
const com_heap_ptr<WAVEFORMATEX> & mix_format,
@@ -2021,6 +2092,7 @@ initialize_iaudioclient3(com_ptr<IAudioClient> & audio_client,
LOG("Could not initialize shared stream with IAudioClient3: error: %lx", hr);
return false;
}
#endif
#define DIRECTION_NAME (direction == eCapture ? "capture" : "render")
@@ -2035,6 +2107,8 @@ setup_wasapi_stream_one_side(cubeb_stream * stm,
cubeb_stream_params * mix_params,
com_ptr<IMMDevice> & device)
{
XASSERT(direction == eCapture || direction == eRender);
HRESULT hr;
bool is_loopback = stream_params->prefs & CUBEB_STREAM_PREF_LOOPBACK;
if (is_loopback && direction != eCapture) {
@@ -2043,6 +2117,10 @@ setup_wasapi_stream_one_side(cubeb_stream * stm,
}
stm->stream_reset_lock.assert_current_thread_owns();
// If user doesn't specify a particular device, we can choose another one when
// the given devid is unavailable.
bool allow_fallback =
direction == eCapture ? !stm->input_device_id : !stm->output_device_id;
bool try_again = false;
// This loops until we find a device that works, or we've exhausted all
// possibilities.
@@ -2092,7 +2170,7 @@ setup_wasapi_stream_one_side(cubeb_stream * stm,
DIRECTION_NAME, hr);
// A particular device can't be activated because it has been
// unplugged, try fall back to the default audio device.
if (devid && hr == AUDCLNT_E_DEVICE_INVALIDATED) {
if (devid && hr == AUDCLNT_E_DEVICE_INVALIDATED && allow_fallback) {
LOG("Trying again with the default %s audio device.", DIRECTION_NAME);
devid = nullptr;
device = nullptr;
@@ -2169,40 +2247,37 @@ setup_wasapi_stream_one_side(cubeb_stream * stm,
flags |= AUDCLNT_STREAMFLAGS_EVENTCALLBACK;
}
// Sanity check the latency, it may be that the device doesn't support it.
REFERENCE_TIME minimum_period;
REFERENCE_TIME default_period;
hr = audio_client->GetDevicePeriod(&default_period, &minimum_period);
if (FAILED(hr)) {
LOG("Could not get device period: %lx", hr);
return CUBEB_ERROR;
}
REFERENCE_TIME latency_hns = frames_to_hns(stream_params->rate, stm->latency);
stm->input_bluetooth_handsfree = false;
cubeb_device_info device_info;
if (wasapi_create_device(stm->context, device_info,
stm->device_enumerator.get(),
device.get()) == CUBEB_OK) {
const char * HANDSFREE_TAG = "BTHHFENUM";
size_t len = sizeof(HANDSFREE_TAG);
if (direction == eCapture) {
uint32_t default_period_frames =
hns_to_frames(device_info.default_rate, default_period);
// Adjust input latency and check if input is using bluetooth handsfree
// protocol.
if (direction == eCapture) {
stm->input_bluetooth_handsfree = false;
wasapi_default_devices default_devices(stm->device_enumerator.get());
cubeb_device_info device_info;
if (wasapi_create_device(stm->context, device_info,
stm->device_enumerator.get(), device.get(),
&default_devices) == CUBEB_OK) {
// This multiplicator has been found empirically.
XASSERT(device_info.latency_hi > 0);
uint32_t latency_frames = device_info.latency_hi * 8;
LOG("Input: latency increased to %u frames from a default of %u",
latency_frames, device_info.latency_hi);
latency_hns = frames_to_hns(device_info.default_rate, latency_frames);
const char * HANDSFREE_TAG = "BTHHFENUM";
size_t len = sizeof(HANDSFREE_TAG);
if (strlen(device_info.group_id) >= len &&
strncmp(device_info.group_id, HANDSFREE_TAG, len) == 0) {
LOG("Input device is using bluetooth handsfree protocol");
stm->input_bluetooth_handsfree = true;
}
// This multiplicator has been found empirically.
uint32_t latency_frames = default_period_frames * 8;
LOG("Input: latency increased to %u frames from a default of %u",
latency_frames, default_period_frames);
latency_hns = frames_to_hns(device_info.default_rate, latency_frames);
wasapi_destroy_device(&device_info);
} else {
LOG("Could not get cubeb_device_info. Skip customizing input settings");
}
wasapi_destroy_device(&device_info);
} else {
LOG("Could not get cubeb_device_info.");
}
if (stream_params->prefs & CUBEB_STREAM_PREF_RAW) {
@@ -2258,8 +2333,10 @@ setup_wasapi_stream_one_side(cubeb_stream * stm,
#undef DIRECTION_NAME
void
wasapi_find_matching_output_device(cubeb_stream * stm)
// Returns a non-null cubeb_devid if we find a matched device, or nullptr
// otherwise.
cubeb_devid
wasapi_find_bt_handsfree_output_device(cubeb_stream * stm)
{
HRESULT hr;
cubeb_device_info * input_device = nullptr;
@@ -2268,19 +2345,21 @@ wasapi_find_matching_output_device(cubeb_stream * stm)
// Only try to match to an output device if the input device is a bluetooth
// device that is using the handsfree protocol
if (!stm->input_bluetooth_handsfree) {
return;
return nullptr;
}
wchar_t * tmp = nullptr;
hr = stm->input_device->GetId(&tmp);
if (FAILED(hr)) {
LOG("Couldn't get input device id in wasapi_find_matching_output_device");
return;
LOG("Couldn't get input device id in "
"wasapi_find_bt_handsfree_output_device");
return nullptr;
}
com_heap_ptr<wchar_t> device_id(tmp);
cubeb_devid input_device_id = intern_device_id(stm->context, device_id.get());
cubeb_devid input_device_id = reinterpret_cast<cubeb_devid>(
intern_device_id(stm->context, device_id.get()));
if (!input_device_id) {
return;
return nullptr;
}
int rv = wasapi_enumerate_devices(
@@ -2288,7 +2367,7 @@ wasapi_find_matching_output_device(cubeb_stream * stm)
(cubeb_device_type)(CUBEB_DEVICE_TYPE_INPUT | CUBEB_DEVICE_TYPE_OUTPUT),
&collection);
if (rv != CUBEB_OK) {
return;
return nullptr;
}
// Find the input device, and then find the output device with the same group
@@ -2300,19 +2379,36 @@ wasapi_find_matching_output_device(cubeb_stream * stm)
}
}
for (uint32_t i = 0; i < collection.count; i++) {
cubeb_device_info & dev = collection.device[i];
if (dev.type == CUBEB_DEVICE_TYPE_OUTPUT && dev.group_id && input_device &&
!strcmp(dev.group_id, input_device->group_id) &&
dev.default_rate == input_device->default_rate) {
LOG("Found matching device for %s: %s", input_device->friendly_name,
dev.friendly_name);
stm->output_device_id =
utf8_to_wstr(reinterpret_cast<char const *>(dev.devid));
cubeb_devid matched_output = nullptr;
if (input_device) {
for (uint32_t i = 0; i < collection.count; i++) {
cubeb_device_info & dev = collection.device[i];
if (dev.type == CUBEB_DEVICE_TYPE_OUTPUT && dev.group_id &&
!strcmp(dev.group_id, input_device->group_id) &&
dev.default_rate == input_device->default_rate) {
LOG("Found matching device for %s: %s", input_device->friendly_name,
dev.friendly_name);
matched_output = dev.devid;
break;
}
}
}
wasapi_device_collection_destroy(stm->context, &collection);
return matched_output;
}
std::unique_ptr<wchar_t[]>
copy_wide_string(const wchar_t * src)
{
XASSERT(src);
size_t len = wcslen(src);
std::unique_ptr<wchar_t[]> copy(new wchar_t[len + 1]);
if (wcsncpy_s(copy.get(), len + 1, src, len) != 0) {
return nullptr;
}
return copy;
}
int
@@ -2325,6 +2421,17 @@ setup_wasapi_stream(cubeb_stream * stm)
XASSERT((!stm->output_client || !stm->input_client) &&
"WASAPI stream already setup, close it first.");
std::unique_ptr<const wchar_t[]> selected_output_device_id;
if (stm->output_device_id) {
if (std::unique_ptr<wchar_t[]> tmp =
move(copy_wide_string(stm->output_device_id.get()))) {
selected_output_device_id = move(tmp);
} else {
LOG("Failed to copy output device identifier.");
return CUBEB_ERROR;
}
}
if (has_input(stm)) {
LOG("(%p) Setup capture: device=%p", stm, stm->input_device_id.get());
rv = setup_wasapi_stream_one_side(
@@ -2356,8 +2463,12 @@ setup_wasapi_stream(cubeb_stream * stm)
// device, and the default device is the same bluetooth device, pick the
// right output device, running at the same rate and with the same protocol
// as the input.
if (!stm->output_device_id) {
wasapi_find_matching_output_device(stm);
if (!selected_output_device_id) {
cubeb_devid matched = wasapi_find_bt_handsfree_output_device(stm);
if (matched) {
selected_output_device_id =
move(utf8_to_wstr(reinterpret_cast<char const *>(matched)));
}
}
}
@@ -2371,23 +2482,24 @@ setup_wasapi_stream(cubeb_stream * stm)
stm->output_stream_params.channels = stm->input_stream_params.channels;
stm->output_stream_params.layout = stm->input_stream_params.layout;
if (stm->input_device_id) {
size_t len = wcslen(stm->input_device_id.get());
std::unique_ptr<wchar_t[]> tmp(new wchar_t[len + 1]);
if (wcsncpy_s(tmp.get(), len + 1, stm->input_device_id.get(), len) != 0) {
LOG("Failed to copy device identifier while copying input stream"
" configuration to output stream configuration to drive loopback.");
if (std::unique_ptr<wchar_t[]> tmp =
move(copy_wide_string(stm->input_device_id.get()))) {
XASSERT(!selected_output_device_id);
selected_output_device_id = move(tmp);
} else {
LOG("Failed to copy device identifier while copying input stream "
"configuration to output stream configuration to drive loopback.");
return CUBEB_ERROR;
}
stm->output_device_id = move(tmp);
}
stm->has_dummy_output = true;
}
if (has_output(stm)) {
LOG("(%p) Setup render: device=%p", stm, stm->output_device_id.get());
LOG("(%p) Setup render: device=%p", stm, selected_output_device_id.get());
rv = setup_wasapi_stream_one_side(
stm, &stm->output_stream_params, stm->output_device_id.get(), eRender,
__uuidof(IAudioRenderClient), stm->output_client,
stm, &stm->output_stream_params, selected_output_device_id.get(),
eRender, __uuidof(IAudioRenderClient), stm->output_client,
&stm->output_buffer_frame_count, stm->refill_event, stm->render_client,
&stm->output_mix_params, stm->output_device);
if (rv != CUBEB_OK) {
@@ -2448,10 +2560,11 @@ setup_wasapi_stream(cubeb_stream * stm)
stm->resampler.reset(cubeb_resampler_create(
stm, has_input(stm) ? &input_params : nullptr,
has_output(stm) ? &output_params : nullptr, target_sample_rate,
stm->data_callback, stm->user_ptr,
has_output(stm) && !stm->has_dummy_output ? &output_params : nullptr,
target_sample_rate, stm->data_callback, stm->user_ptr,
stm->voice ? CUBEB_RESAMPLER_QUALITY_VOIP
: CUBEB_RESAMPLER_QUALITY_DESKTOP));
: CUBEB_RESAMPLER_QUALITY_DESKTOP,
CUBEB_RESAMPLER_RECLOCK_NONE));
if (!stm->resampler) {
LOG("Could not get a resampler");
return CUBEB_ERROR;
@@ -2616,12 +2729,15 @@ wasapi_stream_init(cubeb * context, cubeb_stream ** stream,
return rv;
}
if (!((input_stream_params ? (input_stream_params->prefs &
CUBEB_STREAM_PREF_DISABLE_DEVICE_SWITCHING)
: 0) ||
(output_stream_params ? (output_stream_params->prefs &
CUBEB_STREAM_PREF_DISABLE_DEVICE_SWITCHING)
: 0))) {
// Follow the system default devices when not specifying devices explicitly
// and CUBEB_STREAM_PREF_DISABLE_DEVICE_SWITCHING is not set.
if ((!input_device && input_stream_params &&
!(input_stream_params->prefs &
CUBEB_STREAM_PREF_DISABLE_DEVICE_SWITCHING)) ||
(!output_device && output_stream_params &&
!(output_stream_params->prefs &
CUBEB_STREAM_PREF_DISABLE_DEVICE_SWITCHING))) {
LOG("Follow the system default input or/and output devices");
HRESULT hr = register_notification_client(stm.get());
if (FAILED(hr)) {
/* this is not fatal, we can still play audio, but we won't be able
@@ -3011,31 +3127,30 @@ static com_ptr<IMMDevice> wasapi_get_device_node(
return ret;
}
static BOOL
wasapi_is_default_device(EDataFlow flow, ERole role, LPCWSTR device_id,
IMMDeviceEnumerator * enumerator)
static com_heap_ptr<wchar_t>
wasapi_get_default_device_id(EDataFlow flow, ERole role,
IMMDeviceEnumerator * enumerator)
{
BOOL ret = FALSE;
com_ptr<IMMDevice> dev;
HRESULT hr;
hr = enumerator->GetDefaultAudioEndpoint(flow, role, dev.receive());
HRESULT hr = enumerator->GetDefaultAudioEndpoint(flow, role, dev.receive());
if (SUCCEEDED(hr)) {
wchar_t * tmp = nullptr;
if (SUCCEEDED(dev->GetId(&tmp))) {
com_heap_ptr<wchar_t> defdevid(tmp);
ret = (wcscmp(defdevid.get(), device_id) == 0);
com_heap_ptr<wchar_t> devid(tmp);
return devid;
}
}
return ret;
return nullptr;
}
/* `ret` must be deallocated with `wasapi_destroy_device`, iff the return value
* of this function is `CUBEB_OK`. */
int
wasapi_create_device(cubeb * ctx, cubeb_device_info & ret,
IMMDeviceEnumerator * enumerator, IMMDevice * dev)
IMMDeviceEnumerator * enumerator, IMMDevice * dev,
wasapi_default_devices * defaults)
{
com_ptr<IMMEndpoint> endpoint;
com_ptr<IMMDevice> devnode;
@@ -3046,6 +3161,8 @@ wasapi_create_device(cubeb * ctx, cubeb_device_info & ret,
REFERENCE_TIME def_period, min_period;
HRESULT hr;
XASSERT(enumerator && dev && defaults);
// zero-out to be able to safely delete the pointers to friendly_name and
// group_id at all time in this function.
PodZero(&ret, 1);
@@ -3133,19 +3250,14 @@ wasapi_create_device(cubeb * ctx, cubeb_device_info & ret,
}
ret.preferred = CUBEB_DEVICE_PREF_NONE;
if (wasapi_is_default_device(flow, eConsole, device_id.get(), enumerator)) {
if (defaults->is_default(flow, eConsole, device_id.get())) {
ret.preferred =
(cubeb_device_pref)(ret.preferred | CUBEB_DEVICE_PREF_MULTIMEDIA);
}
if (wasapi_is_default_device(flow, eCommunications, device_id.get(),
enumerator)) {
(cubeb_device_pref)(ret.preferred | CUBEB_DEVICE_PREF_MULTIMEDIA |
CUBEB_DEVICE_PREF_NOTIFICATION);
} else if (defaults->is_default(flow, eCommunications, device_id.get())) {
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);
}
if (flow == eRender) {
ret.type = CUBEB_DEVICE_TYPE_OUTPUT;
@@ -3229,14 +3341,17 @@ wasapi_enumerate_devices(cubeb * context, cubeb_device_type type,
return CUBEB_ERROR;
}
if (type == CUBEB_DEVICE_TYPE_OUTPUT)
wasapi_default_devices default_devices(enumerator.get());
if (type == CUBEB_DEVICE_TYPE_OUTPUT) {
flow = eRender;
else if (type == CUBEB_DEVICE_TYPE_INPUT)
} else if (type == CUBEB_DEVICE_TYPE_INPUT) {
flow = eCapture;
else if (type & (CUBEB_DEVICE_TYPE_INPUT | CUBEB_DEVICE_TYPE_OUTPUT))
} else if (type & (CUBEB_DEVICE_TYPE_INPUT | CUBEB_DEVICE_TYPE_OUTPUT)) {
flow = eAll;
else
} else {
return CUBEB_ERROR;
}
hr = enumerator->EnumAudioEndpoints(flow, DEVICE_STATEMASK_ALL,
collection.receive());
@@ -3264,7 +3379,7 @@ wasapi_enumerate_devices(cubeb * context, cubeb_device_type type,
continue;
}
if (wasapi_create_device(context, devices[out->count], enumerator.get(),
dev.get()) == CUBEB_OK) {
dev.get(), &default_devices) == CUBEB_OK) {
out->count += 1;
}
}
@@ -3294,6 +3409,7 @@ wasapi_register_device_collection_changed(
cubeb_device_collection_changed_callback collection_changed_callback,
void * user_ptr)
{
auto_lock lock(context->lock);
if (devtype == CUBEB_DEVICE_TYPE_UNKNOWN) {
return CUBEB_ERROR_INVALID_PARAMETER;
}

View File

@@ -73,17 +73,6 @@
#define CUBEB_STREAM_MAX 32
#define NBUFS 4
const GUID KSDATAFORMAT_SUBTYPE_PCM = {
0x00000001,
0x0000,
0x0010,
{0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71}};
const GUID KSDATAFORMAT_SUBTYPE_IEEE_FLOAT = {
0x00000003,
0x0000,
0x0010,
{0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71}};
struct cubeb_stream_item {
SLIST_ENTRY head;
cubeb_stream * stream;

View File

@@ -137,26 +137,20 @@ void device_collection_changed_callback(cubeb * context, void * user)
" called when opening a stream";
}
TEST(cubeb, duplex_collection_change)
void
duplex_collection_change_impl(cubeb * ctx)
{
cubeb *ctx;
cubeb_stream *stream;
cubeb_stream * stream;
cubeb_stream_params input_params;
cubeb_stream_params output_params;
int r;
uint32_t latency_frames = 0;
r = common_init(&ctx, "Cubeb duplex example with collection change");
ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb library";
r = cubeb_register_device_collection_changed(ctx,
static_cast<cubeb_device_type>(CUBEB_DEVICE_TYPE_INPUT),
device_collection_changed_callback,
nullptr);
r = cubeb_register_device_collection_changed(
ctx, static_cast<cubeb_device_type>(CUBEB_DEVICE_TYPE_INPUT),
device_collection_changed_callback, nullptr);
ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb stream";
std::unique_ptr<cubeb, decltype(&cubeb_destroy)>
cleanup_cubeb_at_exit(ctx, cubeb_destroy);
/* typical user-case: mono input, stereo output, low latency. */
input_params.format = STREAM_FORMAT;
@@ -173,13 +167,43 @@ TEST(cubeb, duplex_collection_change)
r = cubeb_get_min_latency(ctx, &output_params, &latency_frames);
ASSERT_EQ(r, CUBEB_OK) << "Could not get minimal latency";
r = cubeb_stream_init(ctx, &stream, "Cubeb duplex",
NULL, &input_params, NULL, &output_params,
latency_frames, data_cb_duplex, state_cb_duplex, nullptr);
r = cubeb_stream_init(ctx, &stream, "Cubeb duplex", NULL, &input_params, NULL,
&output_params, latency_frames, data_cb_duplex,
state_cb_duplex, nullptr);
ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb stream";
cubeb_stream_destroy(stream);
}
TEST(cubeb, duplex_collection_change)
{
cubeb * ctx;
int r;
r = common_init(&ctx, "Cubeb duplex example with collection change");
ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb library";
std::unique_ptr<cubeb, decltype(&cubeb_destroy)> cleanup_cubeb_at_exit(
ctx, cubeb_destroy);
duplex_collection_change_impl(ctx);
r = cubeb_register_device_collection_changed(
ctx, static_cast<cubeb_device_type>(CUBEB_DEVICE_TYPE_INPUT), nullptr,
nullptr);
ASSERT_EQ(r, CUBEB_OK);
}
TEST(cubeb, duplex_collection_change_no_unregister)
{
cubeb * ctx;
int r;
r = common_init(&ctx, "Cubeb duplex example with collection change");
ASSERT_EQ(r, CUBEB_OK) << "Error initializing cubeb library";
std::unique_ptr<cubeb, decltype(&cubeb_destroy)> cleanup_cubeb_at_exit(
ctx, [](cubeb * p) noexcept { EXPECT_DEATH(cubeb_destroy(p), ""); });
duplex_collection_change_impl(ctx);
}
long data_cb_input(cubeb_stream * stream, void * user, const void * inputbuffer, void * outputbuffer, long nframes)
{
if (stream == NULL || inputbuffer == NULL || outputbuffer != NULL) {

View File

@@ -338,7 +338,8 @@ void test_resampler_duplex(uint32_t input_channels, uint32_t output_channels,
cubeb_resampler * resampler =
cubeb_resampler_create((cubeb_stream*)nullptr, &input_params, &output_params, target_rate,
data_cb_resampler, (void*)&state, CUBEB_RESAMPLER_QUALITY_VOIP);
data_cb_resampler, (void*)&state, CUBEB_RESAMPLER_QUALITY_VOIP,
CUBEB_RESAMPLER_RECLOCK_NONE);
long latency = cubeb_resampler_latency(resampler);
@@ -484,8 +485,8 @@ TEST(cubeb, resampler_output_only_noop)
cubeb_resampler * resampler =
cubeb_resampler_create((cubeb_stream*)nullptr, nullptr, &output_params, target_rate,
test_output_only_noop_data_cb, nullptr,
CUBEB_RESAMPLER_QUALITY_VOIP);
CUBEB_RESAMPLER_QUALITY_VOIP,
CUBEB_RESAMPLER_RECLOCK_NONE);
const long out_frames = 128;
float out_buffer[out_frames];
long got;
@@ -523,7 +524,8 @@ TEST(cubeb, resampler_drain)
cubeb_resampler * resampler =
cubeb_resampler_create((cubeb_stream*)nullptr, nullptr, &output_params, target_rate,
test_drain_data_cb, &cb_count,
CUBEB_RESAMPLER_QUALITY_VOIP);
CUBEB_RESAMPLER_QUALITY_VOIP,
CUBEB_RESAMPLER_RECLOCK_NONE);
const long out_frames = 128;
float out_buffer[out_frames];
@@ -572,7 +574,8 @@ TEST(cubeb, resampler_passthrough_output_only)
cubeb_resampler * resampler =
cubeb_resampler_create((cubeb_stream*)nullptr, nullptr, &output_params,
target_rate, cb_passthrough_resampler_output, nullptr,
CUBEB_RESAMPLER_QUALITY_VOIP);
CUBEB_RESAMPLER_QUALITY_VOIP,
CUBEB_RESAMPLER_RECLOCK_NONE);
float output_buffer[output_channels * 256];
@@ -616,7 +619,8 @@ TEST(cubeb, resampler_passthrough_input_only)
cubeb_resampler * resampler =
cubeb_resampler_create((cubeb_stream*)nullptr, &input_params, nullptr,
target_rate, cb_passthrough_resampler_input, nullptr,
CUBEB_RESAMPLER_QUALITY_VOIP);
CUBEB_RESAMPLER_QUALITY_VOIP,
CUBEB_RESAMPLER_RECLOCK_NONE);
float input_buffer[input_channels * 256];
@@ -737,7 +741,8 @@ TEST(cubeb, resampler_passthrough_duplex_callback_reordering)
cubeb_resampler * resampler =
cubeb_resampler_create((cubeb_stream*)nullptr, &input_params, &output_params,
target_rate, cb_passthrough_resampler_duplex, &c,
CUBEB_RESAMPLER_QUALITY_VOIP);
CUBEB_RESAMPLER_QUALITY_VOIP,
CUBEB_RESAMPLER_RECLOCK_NONE);
const long BUF_BASE_SIZE = 256;
float input_buffer_prebuffer[input_channels * BUF_BASE_SIZE * 2];
@@ -820,7 +825,7 @@ TEST(cubeb, resampler_drift_drop_data)
cubeb_resampler * resampler =
cubeb_resampler_create((cubeb_stream*)nullptr, &input_params, &output_params,
target_rate, cb_passthrough_resampler_duplex, &c,
CUBEB_RESAMPLER_QUALITY_VOIP);
CUBEB_RESAMPLER_QUALITY_VOIP, CUBEB_RESAMPLER_RECLOCK_NONE);
const long BUF_BASE_SIZE = 256;

View File

@@ -7,6 +7,7 @@
#include <cstring>
#include <inttypes.h>
#include <iostream>
#include <vector>
#ifdef _WIN32
#include <objbase.h> // Used by CoInitialize()
#endif
@@ -35,6 +36,32 @@ static const char* state_to_string(cubeb_state state) {
}
}
static const char* device_type_to_string(cubeb_device_type type) {
switch (type) {
case CUBEB_DEVICE_TYPE_INPUT:
return "input";
case CUBEB_DEVICE_TYPE_OUTPUT:
return "output";
case CUBEB_DEVICE_TYPE_UNKNOWN:
return "unknown";
default:
assert(false);
}
}
static const char* device_state_to_string(cubeb_device_state state) {
switch (state) {
case CUBEB_DEVICE_STATE_DISABLED:
return "disabled";
case CUBEB_DEVICE_STATE_UNPLUGGED:
return "unplugged";
case CUBEB_DEVICE_STATE_ENABLED:
return "enabled";
default:
assert(false);
}
}
void print_log(const char* msg, ...) {
va_list args;
va_start(args, msg);
@@ -48,6 +75,7 @@ public:
~cubeb_client() {}
bool init(char const * backend_name = nullptr);
cubeb_devid select_device(cubeb_device_type type) const;
bool init_stream();
bool start_stream();
bool stop_stream();
@@ -70,7 +98,10 @@ public:
bool unregister_device_collection_changed(cubeb_device_type devtype) const;
cubeb_stream_params output_params = {};
cubeb_devid output_device = nullptr;
cubeb_stream_params input_params = {};
cubeb_devid input_device = nullptr;
void force_drain() { _force_drain = true; }
@@ -81,8 +112,6 @@ private:
cubeb* context = nullptr;
cubeb_stream* stream = nullptr;
cubeb_devid output_device = nullptr;
cubeb_devid input_device = nullptr;
/* Accessed only from client and audio thread. */
std::atomic<uint32_t> _rate = {0};
@@ -492,6 +521,56 @@ bool choose_action(cubeb_client& cl, operation_data * op, int c) {
return true; // Loop up
}
cubeb_devid cubeb_client::select_device(cubeb_device_type type) const
{
assert(type == CUBEB_DEVICE_TYPE_INPUT || type == CUBEB_DEVICE_TYPE_OUTPUT);
cubeb_device_collection collection;
if (cubeb_enumerate_devices(context, type, &collection) ==
CUBEB_ERROR_NOT_SUPPORTED) {
fprintf(stderr,
"Not support %s device selection. Force to use default device\n",
device_type_to_string(type));
return nullptr;
}
assert(collection.count);
fprintf(stderr, "Found %zu %s devices. Choose one:\n", collection.count,
device_type_to_string(type));
std::vector<cubeb_devid> devices;
devices.emplace_back(nullptr);
fprintf(stderr, "# 0\n\tname: system default device\n");
for (size_t i = 0; i < collection.count; i++) {
assert(collection.device[i].type == type);
fprintf(stderr,
"# %zu %s\n"
"\tname: %s\n"
"\tdevice id: %s\n"
"\tmax channels: %u\n"
"\tstate: %s\n",
devices.size(),
collection.device[i].preferred ? " (PREFERRED)" : "",
collection.device[i].friendly_name, collection.device[i].device_id,
collection.device[i].max_channels,
device_state_to_string(collection.device[i].state));
devices.emplace_back(collection.device[i].devid);
}
cubeb_device_collection_destroy(context, &collection);
size_t number;
std::cout << "Enter device number: ";
std::cin >> number;
while (!std::cin || number >= devices.size()) {
std::cin.clear();
std::cin.ignore(100, '\n');
std::cout << "Error: Please enter a valid numeric input. Enter again: ";
std::cin >> number;
}
return devices[number];
}
int main(int argc, char* argv[]) {
#ifdef _WIN32
CoInitialize(nullptr);
@@ -523,7 +602,7 @@ int main(int argc, char* argv[]) {
bool res = false;
cubeb_client cl;
cl.activate_log(CUBEB_LOG_DISABLED);
cl.activate_log(CUBEB_LOG_NORMAL);
fprintf(stderr, "Log level is DISABLED\n");
cl.init(/* default backend */);
@@ -540,10 +619,12 @@ int main(int argc, char* argv[]) {
}
} else {
if (op.pm == PLAYBACK || op.pm == DUPLEX || op.pm == LATENCY_TESTING) {
cl.output_device = cl.select_device(CUBEB_DEVICE_TYPE_OUTPUT);
cl.output_params = {CUBEB_SAMPLE_FLOAT32NE, op.rate, DEFAULT_OUTPUT_CHANNELS,
CUBEB_LAYOUT_STEREO, CUBEB_STREAM_PREF_NONE};
}
if (op.pm == RECORD || op.pm == DUPLEX || op.pm == LATENCY_TESTING) {
cl.input_device = cl.select_device(CUBEB_DEVICE_TYPE_INPUT);
cl.input_params = {CUBEB_SAMPLE_FLOAT32NE, op.rate, DEFAULT_INPUT_CHANNELS, CUBEB_LAYOUT_UNDEFINED, CUBEB_STREAM_PREF_NONE};
}
if (op.pm == LATENCY_TESTING) {