dep/cubeb: Update to 1d66483

This commit is contained in:
Connor McLaughlin 2020-10-24 21:05:37 +10:00
parent 1b618b8c46
commit 045866506f
20 changed files with 2012 additions and 296 deletions

View File

@ -110,6 +110,18 @@ if(USE_OPENSL)
target_link_libraries(cubeb PRIVATE OpenSLES)
endif()
check_include_files(sys/soundcard.h HAVE_SYS_SOUNDCARD_H)
if(HAVE_SYS_SOUNDCARD_H)
try_compile(USE_OSS "${PROJECT_BINARY_DIR}/compile_tests"
${PROJECT_SOURCE_DIR}/cmake/compile_tests/oss_is_v4.c)
if(USE_OSS)
target_sources(cubeb PRIVATE
src/cubeb_oss.c)
target_compile_definitions(cubeb PRIVATE USE_OSS)
target_link_libraries(cubeb PRIVATE pthread)
endif()
endif()
check_include_files(android/log.h USE_AUDIOTRACK)
if(USE_AUDIOTRACK)
target_sources(cubeb PRIVATE

View File

@ -230,12 +230,22 @@ typedef enum {
CUBEB_STREAM_PREF_DISABLE_DEVICE_SWITCHING = 0x02, /**< Disable switching
default device on OS
changes. */
CUBEB_STREAM_PREF_VOICE = 0x04 /**< This stream is going to transport voice data.
CUBEB_STREAM_PREF_VOICE = 0x04, /**< This stream is going to transport voice data.
Depending on the backend and platform, this can
change the audio input or output devices
selected, as well as the quality of the stream,
for example to accomodate bluetooth SCO modes on
bluetooth devices. */
CUBEB_STREAM_PREF_RAW = 0x08, /**< Windows only. Bypass all signal processing
except for always on APO, driver and hardware. */
CUBEB_STREAM_PREF_PERSIST = 0x10, /**< Request that the volume and mute settings
should persist across restarts of the stream
and/or application. May not be honored for
all backends and platforms. */
CUBEB_STREAM_PREF_JACK_NO_AUTO_CONNECT = 0x20 /**< Don't automatically try to connect
ports. Only affects the jack
backend. */
} cubeb_stream_prefs;
/** Stream format initialization parameters. */
@ -487,11 +497,17 @@ CUBEB_EXPORT void cubeb_destroy(cubeb * context);
cubeb stream.
@param stream_name A name for this stream.
@param input_device Device for the input side of the stream. If NULL the
default input device is used.
default input device is used. Passing a valid cubeb_devid
means the stream only ever uses that device. Passing a NULL
cubeb_devid allows the stream to follow that device type's
OS default.
@param input_stream_params Parameters for the input side of the stream, or
NULL if this stream is output only.
@param output_device Device for the output side of the stream. If NULL the
default output device is used.
default output device is used. Passing a valid cubeb_devid
means the stream only ever uses that device. Passing a NULL
cubeb_devid allows the stream to follow that device type's
OS default.
@param output_stream_params Parameters for the output side of the stream, or
NULL if this stream is input only.
@param latency_frames Stream latency in frames. Valid range
@ -559,6 +575,16 @@ CUBEB_EXPORT int cubeb_stream_get_position(cubeb_stream * stream, uint64_t * pos
@retval CUBEB_ERROR */
CUBEB_EXPORT int cubeb_stream_get_latency(cubeb_stream * stream, uint32_t * latency);
/** Get the input latency for this stream, in frames. This is the number of
frames between the time the audio input devices records the data, and they
are available in the data callback.
This returns CUBEB_ERROR when the stream is output-only.
@param stream
@param latency Current approximate stream latency in frames.
@retval CUBEB_OK
@retval CUBEB_ERROR_NOT_SUPPORTED
@retval CUBEB_ERROR */
CUBEB_EXPORT int cubeb_stream_get_input_latency(cubeb_stream * stream, uint32_t * latency);
/** Set the volume for a stream.
@param stream the stream for which to adjust the volume.
@param volume a float between 0.0 (muted) and 1.0 (maximum volume)
@ -568,6 +594,14 @@ CUBEB_EXPORT int cubeb_stream_get_latency(cubeb_stream * stream, uint32_t * late
@retval CUBEB_ERROR_NOT_SUPPORTED */
CUBEB_EXPORT int cubeb_stream_set_volume(cubeb_stream * stream, float volume);
/** Change a stream's name.
@param stream the stream for which to set the name.
@param stream_name the new name for the stream
@retval CUBEB_OK
@retval CUBEB_ERROR_INVALID_PARAMETER if any pointer is invalid
@retval CUBEB_ERROR_NOT_SUPPORTED */
CUBEB_EXPORT int cubeb_stream_set_name(cubeb_stream * stream, char const * stream_name);
/** Get the current output device for this stream.
@param stm the stream for which to query the current output device
@param device a pointer in which the current output device will be stored.

View File

@ -63,7 +63,9 @@ struct cubeb_ops {
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_get_input_latency)(cubeb_stream * stream, uint32_t * latency);
int (* stream_set_volume)(cubeb_stream * stream, float volumes);
int (* stream_set_name)(cubeb_stream * stream, char const * stream_name);
int (* stream_get_current_device)(cubeb_stream * stream,
cubeb_device ** const device);
int (* stream_device_destroy)(cubeb_stream * stream,

View File

@ -60,6 +60,9 @@ int sun_init(cubeb ** context, char const * context_name);
#if defined(USE_OPENSL)
int opensl_init(cubeb ** context, char const * context_name);
#endif
#if defined(USE_OSS)
int oss_init(cubeb ** context, char const * context_name);
#endif
#if defined(USE_AUDIOTRACK)
int audiotrack_init(cubeb ** context, char const * context_name);
#endif
@ -80,7 +83,7 @@ validate_stream_params(cubeb_stream_params * input_stream_params,
}
if (input_stream_params) {
if (input_stream_params->rate < 1000 || input_stream_params->rate > 192000 ||
input_stream_params->channels < 1 || input_stream_params->channels > 8) {
input_stream_params->channels < 1 || input_stream_params->channels > UINT8_MAX) {
return CUBEB_ERROR_INVALID_FORMAT;
}
}
@ -165,6 +168,10 @@ cubeb_init(cubeb ** context, char const * context_name, char const * backend_nam
} else if (!strcmp(backend_name, "opensl")) {
#if defined(USE_OPENSL)
init_oneshot = opensl_init;
#endif
} else if (!strcmp(backend_name, "oss")) {
#if defined(USE_OSS)
init_oneshot = oss_init;
#endif
} else if (!strcmp(backend_name, "audiotrack")) {
#if defined(USE_AUDIOTRACK)
@ -200,12 +207,15 @@ cubeb_init(cubeb ** context, char const * context_name, char const * backend_nam
#if defined(USE_ALSA)
alsa_init,
#endif
#if defined(USE_AUDIOUNIT)
audiounit_init,
#if defined (USE_OSS)
oss_init,
#endif
#if defined(USE_AUDIOUNIT_RUST)
audiounit_rust_init,
#endif
#if defined(USE_AUDIOUNIT)
audiounit_init,
#endif
#if defined(USE_WASAPI)
wasapi_init,
#endif
@ -420,6 +430,20 @@ cubeb_stream_get_latency(cubeb_stream * stream, uint32_t * latency)
return stream->context->ops->stream_get_latency(stream, latency);
}
int
cubeb_stream_get_input_latency(cubeb_stream * stream, uint32_t * latency)
{
if (!stream || !latency) {
return CUBEB_ERROR_INVALID_PARAMETER;
}
if (!stream->context->ops->stream_get_input_latency) {
return CUBEB_ERROR_NOT_SUPPORTED;
}
return stream->context->ops->stream_get_input_latency(stream, latency);
}
int
cubeb_stream_set_volume(cubeb_stream * stream, float volume)
{
@ -434,6 +458,20 @@ cubeb_stream_set_volume(cubeb_stream * stream, float volume)
return stream->context->ops->stream_set_volume(stream, volume);
}
int
cubeb_stream_set_name(cubeb_stream * stream, char const * stream_name)
{
if (!stream || !stream_name) {
return CUBEB_ERROR_INVALID_PARAMETER;
}
if (!stream->context->ops->stream_set_name) {
return CUBEB_ERROR_NOT_SUPPORTED;
}
return stream->context->ops->stream_set_name(stream, stream_name);
}
int cubeb_stream_get_current_device(cubeb_stream * stream,
cubeb_device ** const device)
{

View File

@ -1444,7 +1444,9 @@ static struct cubeb_ops const alsa_ops = {
.stream_reset_default_device = NULL,
.stream_get_position = alsa_stream_get_position,
.stream_get_latency = alsa_stream_get_latency,
.stream_get_input_latency = NULL,
.stream_set_volume = alsa_stream_set_volume,
.stream_set_name = NULL,
.stream_get_current_device = NULL,
.stream_device_destroy = NULL,
.stream_register_device_changed_callback = NULL,

View File

@ -433,7 +433,9 @@ static struct cubeb_ops const audiotrack_ops = {
.stream_reset_default_device = NULL,
.stream_get_position = audiotrack_stream_get_position,
.stream_get_latency = audiotrack_stream_get_latency,
.stream_get_input_latency = NULL,
.stream_set_volume = audiotrack_stream_set_volume,
.stream_set_name = NULL,
.stream_get_current_device = NULL,
.stream_device_destroy = NULL,
.stream_register_device_changed_callback = NULL,

View File

@ -2871,6 +2871,15 @@ audiounit_stream_destroy_internal(cubeb_stream *stm)
static void
audiounit_stream_destroy(cubeb_stream * stm)
{
int r = audiounit_uninstall_system_changed_callback(stm);
if (r != CUBEB_OK) {
LOG("(%p) Could not uninstall the device changed callback", stm);
}
r = audiounit_uninstall_device_changed_callback(stm);
if (r != CUBEB_OK) {
LOG("(%p) Could not uninstall all device change listeners", stm);
}
if (!stm->shutdown.load()){
auto_lock context_lock(stm->context->mutex);
audiounit_stream_stop_internal(stm);
@ -3612,7 +3621,9 @@ cubeb_ops const audiounit_ops = {
/*.stream_reset_default_device =*/ nullptr,
/*.stream_get_position =*/ audiounit_stream_get_position,
/*.stream_get_latency =*/ audiounit_stream_get_latency,
/*.stream_get_input_latency =*/ NULL,
/*.stream_set_volume =*/ audiounit_stream_set_volume,
/*.stream_set_name =*/ NULL,
/*.stream_get_current_device =*/ audiounit_stream_get_current_device,
/*.stream_device_destroy =*/ audiounit_stream_device_destroy,
/*.stream_register_device_changed_callback =*/ audiounit_stream_register_device_changed_callback,

View File

@ -132,7 +132,9 @@ static struct cubeb_ops const cbjack_ops = {
.stream_reset_default_device = NULL,
.stream_get_position = cbjack_stream_get_position,
.stream_get_latency = cbjack_get_latency,
.stream_get_input_latency = NULL,
.stream_set_volume = cbjack_stream_set_volume,
.stream_set_name = NULL,
.stream_get_current_device = cbjack_stream_get_current_device,
.stream_device_destroy = cbjack_stream_device_destroy,
.stream_register_device_changed_callback = NULL,
@ -237,6 +239,22 @@ load_jack_lib(cubeb * context)
return CUBEB_OK;
}
static void
cbjack_connect_port_out (cubeb_stream * stream, const size_t out_port, const char * const phys_in_port)
{
const char *src_port = api_jack_port_name (stream->output_ports[out_port]);
api_jack_connect (stream->context->jack_client, src_port, phys_in_port);
}
static void
cbjack_connect_port_in (cubeb_stream * stream, const char * const phys_out_port, size_t in_port)
{
const char *src_port = api_jack_port_name (stream->input_ports[in_port]);
api_jack_connect (stream->context->jack_client, phys_out_port, src_port);
}
static int
cbjack_connect_ports (cubeb_stream * stream)
{
@ -256,10 +274,14 @@ cbjack_connect_ports (cubeb_stream * stream)
// Connect outputs to playback
for (unsigned int c = 0; c < stream->out_params.channels && phys_in_ports[c] != NULL; c++) {
const char *src_port = api_jack_port_name (stream->output_ports[c]);
api_jack_connect (stream->context->jack_client, src_port, phys_in_ports[c]);
cbjack_connect_port_out(stream, c, phys_in_ports[c]);
}
// Special case playing mono source in stereo
if (stream->out_params.channels == 1 && phys_in_ports[1] != NULL) {
cbjack_connect_port_out(stream, 0, phys_in_ports[1]);
}
r = CUBEB_OK;
skipplayback:
@ -268,9 +290,7 @@ skipplayback:
}
// Connect inputs to capture
for (unsigned int c = 0; c < stream->in_params.channels && phys_out_ports[c] != NULL; c++) {
const char *src_port = api_jack_port_name (stream->input_ports[c]);
api_jack_connect (stream->context->jack_client, phys_out_ports[c], src_port);
cbjack_connect_port_in(stream, phys_out_ports[c], c);
}
r = CUBEB_OK;
end:
@ -289,9 +309,9 @@ cbjack_xrun_callback(void * arg)
cubeb * ctx = (cubeb *)arg;
float delay = api_jack_get_xrun_delayed_usecs(ctx->jack_client);
int fragments = (int)ceilf( ((delay / 1000000.0) * ctx->jack_sample_rate )
/ (float)(ctx->jack_buffer_size) );
ctx->jack_xruns += fragments;
float fragments = ceilf(((delay / 1000000.0) * ctx->jack_sample_rate) / ctx->jack_buffer_size);
ctx->jack_xruns += (unsigned int)fragments;
return 0;
}
@ -331,9 +351,11 @@ static int
cbjack_process(jack_nframes_t nframes, void * arg)
{
cubeb * ctx = (cubeb *)arg;
int t_jack_xruns = ctx->jack_xruns;
unsigned int t_jack_xruns = ctx->jack_xruns;
int i;
ctx->jack_xruns = 0;
for (int j = 0; j < MAX_STREAMS; j++) {
cubeb_stream *stm = &ctx->streams[j];
float *bufs_out[stm->out_params.channels];
@ -343,10 +365,7 @@ cbjack_process(jack_nframes_t nframes, void * arg)
continue;
// handle xruns by skipping audio that should have been played
for (i = 0; i < t_jack_xruns; i++) {
stm->position += ctx->fragment_size * stm->ratio;
}
ctx->jack_xruns -= t_jack_xruns;
stm->position += t_jack_xruns * ctx->fragment_size * stm->ratio;
if (!stm->ports_ready)
continue;
@ -888,11 +907,13 @@ cbjack_stream_init(cubeb * context, cubeb_stream ** stream, char const * stream_
}
}
if (!input_stream_params->prefs & CUBEB_STREAM_PREF_JACK_NO_AUTO_CONNECT) {
if (cbjack_connect_ports(stm) != CUBEB_OK) {
pthread_mutex_unlock(&stm->mutex);
cbjack_stream_destroy(stm);
return CUBEB_ERROR;
}
}
*stream = stm;

View File

@ -361,7 +361,9 @@ static struct cubeb_ops const kai_ops = {
/*.stream_reset_default_device =*/ NULL,
/*.stream_get_position =*/ kai_stream_get_position,
/*.stream_get_latency = */ kai_stream_get_latency,
/*.stream_get_input_latency = */ NULL,
/*.stream_set_volume =*/ kai_stream_set_volume,
/*.stream_set_name =*/ NULL,
/*.stream_get_current_device =*/ NULL,
/*.stream_device_destroy =*/ NULL,
/*.stream_register_device_changed_callback=*/ NULL,

View File

@ -16,8 +16,15 @@ extern "C" {
#if defined(__GNUC__) || defined(__clang__)
#define PRINTF_FORMAT(fmt, args) __attribute__((format(printf, fmt, args)))
#if defined(__FILE_NAME__)
#define __FILENAME__ __FILE_NAME__
#else
#define __FILENAME__ (__builtin_strrchr(__FILE__, '/') ? __builtin_strrchr(__FILE__, '/') + 1 : __FILE__)
#endif
#else
#define PRINTF_FORMAT(fmt, args)
#include <string.h>
#define __FILENAME__ (strrchr(__FILE__, '/') ? strrchr(__FILE__, '/') + 1 : __FILE__)
#endif
extern cubeb_log_level g_cubeb_log_level;
@ -34,7 +41,7 @@ void cubeb_async_log_reset_threads();
#define LOG_INTERNAL(level, fmt, ...) do { \
if (g_cubeb_log_callback && level <= g_cubeb_log_level) { \
g_cubeb_log_callback("%s:%d: " fmt "\n", __FILE__, __LINE__, ##__VA_ARGS__); \
g_cubeb_log_callback("%s:%d: " fmt "\n", __FILENAME__, __LINE__, ##__VA_ARGS__); \
} \
} while(0)

View File

@ -168,7 +168,8 @@ struct cubeb_stream {
int64_t lastPosition;
int64_t lastPositionTimeStamp;
int64_t lastCompensativePosition;
int voice;
int voice_input;
int voice_output;
};
/* Forward declaration. */
@ -959,7 +960,7 @@ opensl_configure_capture(cubeb_stream * stm, cubeb_stream_params * params)
// Voice recognition is the lowest latency, according to the docs. Camcorder
// uses a microphone that is in the same direction as the camera.
SLint32 streamType = stm->voice ? SL_ANDROID_RECORDING_PRESET_VOICE_RECOGNITION
SLint32 streamType = stm->voice_input ? SL_ANDROID_RECORDING_PRESET_VOICE_RECOGNITION
: SL_ANDROID_RECORDING_PRESET_CAMCORDER;
res = (*recorderConfig)
@ -1185,7 +1186,7 @@ opensl_configure_playback(cubeb_stream * stm, cubeb_stream_params * params) {
}
SLint32 streamType = SL_ANDROID_STREAM_MEDIA;
if (stm->voice) {
if (stm->voice_output) {
streamType = SL_ANDROID_STREAM_VOICE;
}
res = (*playerConfig)->SetConfiguration(playerConfig,
@ -1385,10 +1386,11 @@ opensl_stream_init(cubeb * ctx, cubeb_stream ** stream, char const * stream_name
stm->input_enabled = (input_stream_params) ? 1 : 0;
stm->output_enabled = (output_stream_params) ? 1 : 0;
stm->shutdown = 1;
stm->voice = has_pref_set(input_stream_params, output_stream_params, CUBEB_STREAM_PREF_VOICE);
LOG("cubeb stream prefs: voice: %s", stm->voice ? "true" : "false");
stm->voice_input = has_pref_set(input_stream_params, NULL, CUBEB_STREAM_PREF_VOICE);
stm->voice_output = has_pref_set(NULL, output_stream_params, CUBEB_STREAM_PREF_VOICE);
LOG("cubeb stream prefs: voice_input: %s voice_output: %s", stm->voice_input ? "true" : "false",
stm->voice_output ? "true" : "false");
#ifdef DEBUG
pthread_mutexattr_t attr;
@ -1752,7 +1754,9 @@ static struct cubeb_ops const opensl_ops = {
.stream_reset_default_device = NULL,
.stream_get_position = opensl_stream_get_position,
.stream_get_latency = opensl_stream_get_latency,
.stream_get_input_latency = NULL,
.stream_set_volume = opensl_stream_set_volume,
.stream_set_name = NULL,
.stream_get_current_device = NULL,
.stream_device_destroy = NULL,
.stream_register_device_changed_callback = NULL,

1261
dep/cubeb/src/cubeb_oss.c Normal file

File diff suppressed because it is too large Load Diff

View File

@ -86,6 +86,7 @@
X(pa_mainloop_api_once) \
X(pa_get_library_version) \
X(pa_channel_map_init_auto) \
X(pa_stream_set_name) \
#define MAKE_TYPEDEF(x) static typeof(x) * cubeb_##x;
LIBPULSE_API_VISIT(MAKE_TYPEDEF);
@ -1139,6 +1140,14 @@ volume_success(pa_context *c, int success, void *userdata)
WRAP(pa_threaded_mainloop_signal)(stream->context->mainloop, 0);
}
static void
rename_success(pa_stream *s, int success, void *userdata)
{
cubeb_stream * stream = userdata;
assert(success);
WRAP(pa_threaded_mainloop_signal)(stream->context->mainloop, 0);
}
static int
pulse_stream_set_volume(cubeb_stream * stm, float volume)
{
@ -1183,6 +1192,28 @@ pulse_stream_set_volume(cubeb_stream * stm, float volume)
return CUBEB_OK;
}
static int
pulse_stream_set_name(cubeb_stream * stm, char const * stream_name)
{
if (!stm || !stm->output_stream) {
return CUBEB_ERROR;
}
WRAP(pa_threaded_mainloop_lock)(stm->context->mainloop);
pa_operation * op =
WRAP(pa_stream_set_name)(stm->output_stream, stream_name, rename_success, stm);
if (op) {
operation_wait(stm->context, stm->output_stream, op);
WRAP(pa_operation_unref)(op);
}
WRAP(pa_threaded_mainloop_unlock)(stm->context->mainloop);
return CUBEB_OK;
}
typedef struct {
char * default_sink_name;
char * default_source_name;
@ -1597,7 +1628,9 @@ static struct cubeb_ops const pulse_ops = {
.stream_reset_default_device = NULL,
.stream_get_position = pulse_stream_get_position,
.stream_get_latency = pulse_stream_get_latency,
.stream_get_input_latency = NULL,
.stream_set_volume = pulse_stream_set_volume,
.stream_set_name = pulse_stream_set_name,
.stream_get_current_device = pulse_stream_get_current_device,
.stream_device_destroy = pulse_stream_device_destroy,
.stream_register_device_changed_callback = NULL,

View File

@ -61,8 +61,7 @@ long passthrough_resampler<T>::fill(void * input_buffer, long * input_frames_cou
if (input_buffer) {
assert(input_frames_count);
}
assert((input_buffer && output_buffer &&
*input_frames_count + static_cast<int>(samples_to_frames(internal_input_buffer.length())) >= output_frames) ||
assert((input_buffer && output_buffer) ||
(output_buffer && !input_buffer && (!input_frames_count || *input_frames_count == 0)) ||
(input_buffer && !output_buffer && output_frames == 0));
@ -74,14 +73,26 @@ long passthrough_resampler<T>::fill(void * input_buffer, long * input_frames_cou
if (input_buffer && !output_buffer) {
output_frames = *input_frames_count;
} else if(input_buffer) {
if (internal_input_buffer.length() != 0) {
// In this case we have pending input data left and have
// to first append the input so we can pass it as one pointer
// to the callback
if (internal_input_buffer.length() != 0 ||
*input_frames_count < output_frames) {
// If we have pending input data left and have to first append the input
// so we can pass it as one pointer to the callback. Or this is a glitch.
// It can happen when system's performance is poor. Audible silence is
// being pushed at the end of the short input buffer. An improvement for
// the future is to resample to the output number of frames, when that happens.
internal_input_buffer.push(static_cast<T*>(input_buffer),
frames_to_samples(*input_frames_count));
in_buf = internal_input_buffer.data();
if (internal_input_buffer.length() < frames_to_samples(output_frames)) {
// This is unxpected but it can happen when a glitch occurs. Fill the
// buffer with silence. First keep the actual number of input samples
// used without the silence.
pop_input_count = internal_input_buffer.length();
internal_input_buffer.push_silence(
frames_to_samples(output_frames) - internal_input_buffer.length());
} else {
pop_input_count = frames_to_samples(output_frames);
}
in_buf = internal_input_buffer.data();
} else if(*input_frames_count > output_frames) {
// In this case we have more input that we need output and
// fill the overflowing input into internal_input_buffer
@ -99,15 +110,20 @@ long passthrough_resampler<T>::fill(void * input_buffer, long * input_frames_cou
if (input_buffer) {
if (pop_input_count) {
internal_input_buffer.pop(nullptr, pop_input_count);
}
*input_frames_count = samples_to_frames(pop_input_count);
} else {
*input_frames_count = output_frames;
}
drop_audio_if_needed();
}
return rv;
}
// Explicit instantiation of template class.
template class passthrough_resampler<float>;
template class passthrough_resampler<short>;
template<typename T, typename InputProcessor, typename OutputProcessor>
cubeb_resampler_speex<T, InputProcessor, OutputProcessor>
::cubeb_resampler_speex(InputProcessor * input_processor,
@ -122,17 +138,6 @@ cubeb_resampler_speex<T, InputProcessor, OutputProcessor>
, user_ptr(ptr)
{
if (input_processor && output_processor) {
// Add some delay on the processor that has the lowest delay so that the
// streams are synchronized.
uint32_t in_latency = input_processor->latency();
uint32_t out_latency = output_processor->latency();
if (in_latency > out_latency) {
uint32_t latency_diff = in_latency - out_latency;
output_processor->add_latency(latency_diff);
} else if (in_latency < out_latency) {
uint32_t latency_diff = out_latency - in_latency;
input_processor->add_latency(latency_diff);
}
fill_internal = &cubeb_resampler_speex::fill_internal_duplex;
} else if (input_processor) {
fill_internal = &cubeb_resampler_speex::fill_internal_input;

View File

@ -35,6 +35,7 @@ MOZ_END_STD_NAMESPACE
#include "cubeb_utils.h"
#include "cubeb-speex-resampler.h"
#include "cubeb_resampler.h"
#include "cubeb_log.h"
#include <stdio.h>
/* This header file contains the internal C++ API of the resamplers, for testing. */
@ -191,6 +192,19 @@ public:
speex_resampler = speex_resampler_init(channels, source_rate,
target_rate, quality, &r);
assert(r == RESAMPLER_ERR_SUCCESS && "resampler allocation failure");
uint32_t input_latency = speex_resampler_get_input_latency(speex_resampler);
const size_t LATENCY_SAMPLES = 8192;
T input_buffer[LATENCY_SAMPLES] = {};
T output_buffer[LATENCY_SAMPLES] = {};
uint32_t input_frame_count = input_latency;
uint32_t output_frame_count = LATENCY_SAMPLES;
assert(input_latency * channels <= LATENCY_SAMPLES);
speex_resample(
input_buffer,
&input_frame_count,
output_buffer,
&output_frame_count);
}
/** Destructor, deallocate the resampler */
@ -199,17 +213,6 @@ public:
speex_resampler_destroy(speex_resampler);
}
/** Sometimes, it is necessary to add latency on one way of a two-way
* resampler so that the stream are synchronized. This must be called only on
* a fresh resampler, otherwise, silent samples will be inserted in the
* stream.
* @param frames the number of frames of latency to add. */
void add_latency(size_t frames)
{
additional_latency += frames;
resampling_in_buffer.push_silence(frames_to_samples(frames));
}
/* Fill the resampler with `input_frame_count` frames. */
void input(T * input_buffer, size_t input_frame_count)
{
@ -254,7 +257,14 @@ public:
speex_resample(resampling_in_buffer.data(), &in_len,
resampling_out_buffer.data(), &out_len);
assert(out_len == output_frame_count);
if (out_len < output_frame_count) {
LOGV("underrun during resampling: got %u frames, expected %zu", (unsigned)out_len, output_frame_count);
// silence the rightmost part
T* data = resampling_out_buffer.data();
for (uint32_t i = frames_to_samples(out_len); i < frames_to_samples(output_frame_count); i++) {
data[i] = 0;
}
}
/* This shifts back any unresampled samples to the beginning of the input
buffer. */
@ -283,8 +293,9 @@ public:
* exactly `output_frame_count` resampled frames. This can return a number
* slightly bigger than what is strictly necessary, but it guaranteed that the
* number of output frames will be exactly equal. */
uint32_t input_needed_for_output(uint32_t output_frame_count) const
uint32_t input_needed_for_output(int32_t output_frame_count) const
{
assert(output_frame_count >= 0); // Check overflow
int32_t unresampled_frames_left = samples_to_frames(resampling_in_buffer.length());
int32_t resampled_frames_left = samples_to_frames(resampling_out_buffer.length());
float input_frames_needed =
@ -392,13 +403,6 @@ public:
/* Fill the delay line with some silent frames to add latency. */
delay_input_buffer.push_silence(frames * channels);
}
/* Add some latency to the delay line.
* @param frames the number of frames of latency to add. */
void add_latency(size_t frames)
{
length += frames;
delay_input_buffer.push_silence(frames_to_samples(frames));
}
/** Push some frames into the delay line.
* @parameter buffer the frames to push.
* @parameter frame_count the number of frames in #buffer. */
@ -462,8 +466,9 @@ public:
* @parameter frames_needed the number of frames one want to write into the
* delay_line
* @returns the number of frames one will get. */
size_t input_needed_for_output(uint32_t frames_needed) const
uint32_t input_needed_for_output(int32_t frames_needed) const
{
assert(frames_needed >= 0); // Check overflow
return frames_needed;
}
/** Returns the number of frames produces for `input_frames` frames in input */
@ -526,6 +531,7 @@ cubeb_resampler_create_internal(cubeb_stream * stream,
(output_params && output_params->rate == target_rate)) ||
(input_params && !output_params && (input_params->rate == target_rate)) ||
(output_params && !input_params && (output_params->rate == target_rate))) {
LOG("Input and output sample-rate match, target rate of %dHz", target_rate);
return new passthrough_resampler<T>(stream, callback,
user_ptr,
input_params ? input_params->channels : 0,
@ -576,6 +582,7 @@ cubeb_resampler_create_internal(cubeb_stream * stream,
}
if (input_resampler && output_resampler) {
LOG("Resampling input (%d) and output (%d) to target rate of %dHz", input_params->rate, output_params->rate, target_rate);
return new cubeb_resampler_speex<T,
cubeb_resampler_speex_one_way<T>,
cubeb_resampler_speex_one_way<T>>
@ -583,6 +590,7 @@ cubeb_resampler_create_internal(cubeb_stream * stream,
output_resampler.release(),
stream, callback, user_ptr);
} else if (input_resampler) {
LOG("Resampling input (%d) to target and output rate of %dHz", input_params->rate, target_rate);
return new cubeb_resampler_speex<T,
cubeb_resampler_speex_one_way<T>,
delay_line<T>>
@ -590,6 +598,7 @@ cubeb_resampler_create_internal(cubeb_stream * stream,
output_delay.release(),
stream, callback, user_ptr);
} else {
LOG("Resampling output (%dHz) to target and input rate of %dHz", output_params->rate, target_rate);
return new cubeb_resampler_speex<T,
delay_line<T>,
cubeb_resampler_speex_one_way<T>>

View File

@ -128,7 +128,7 @@ s16_to_float(void *ptr, long nsamp)
static const char *
sndio_get_device()
{
#ifndef __OpenBSD__
#ifdef __linux__
/*
* On other platforms default to sndio devices,
* so cubebs other backends can be used instead.
@ -662,6 +662,7 @@ static struct cubeb_ops const sndio_ops = {
.stream_get_position = sndio_stream_get_position,
.stream_get_latency = sndio_stream_get_latency,
.stream_set_volume = sndio_stream_set_volume,
.stream_set_name = NULL,
.stream_get_current_device = NULL,
.stream_device_destroy = NULL,
.stream_register_device_changed_callback = NULL,

View File

@ -1,5 +1,5 @@
/*
* Copyright © 2019 Nia Alarie
* Copyright © 2019-2020 Nia Alarie <nia@NetBSD.org>
*
* This program is made available under an ISC-style license. See the
* accompanying file LICENSE for details.
@ -9,19 +9,14 @@
#include <fcntl.h>
#include <unistd.h>
#include <pthread.h>
#include <stdbool.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <math.h>
#include <limits.h>
#include "cubeb/cubeb.h"
#include "cubeb-internal.h"
#define BYTES_TO_FRAMES(bytes, channels) \
(bytes / (channels * sizeof(int16_t)))
#define FRAMES_TO_BYTES(frames, channels) \
(frames * (channels * sizeof(int16_t)))
/* Default to 4 + 1 for the default device. */
#ifndef SUN_DEVICE_COUNT
#define SUN_DEVICE_COUNT (5)
@ -41,10 +36,6 @@
#define SUN_DEFAULT_DEVICE "/dev/audio"
#endif
#ifndef SUN_POLL_TIMEOUT
#define SUN_POLL_TIMEOUT (1000)
#endif
#ifndef SUN_BUFFER_FRAMES
#define SUN_BUFFER_FRAMES (32)
#endif
@ -75,26 +66,26 @@ struct cubeb {
struct cubeb_ops const * ops;
};
struct sun_stream {
char name[32];
int fd;
void * buf;
struct audio_info info;
unsigned frame_size; /* precision in bytes * channels */
bool floating;
};
struct cubeb_stream {
struct cubeb * context;
void * user_ptr;
pthread_t thread;
pthread_mutex_t mutex; /* protects running, volume, frames_written */
int floating;
int running;
int play_fd;
int record_fd;
bool running;
float volume;
struct audio_info p_info; /* info for the play fd */
struct audio_info r_info; /* info for the record fd */
struct sun_stream play;
struct sun_stream record;
cubeb_data_callback data_cb;
cubeb_state_callback state_cb;
int16_t * play_buf;
int16_t * record_buf;
float * f_play_buf;
float * f_record_buf;
char input_name[32];
char output_name[32];
uint64_t frames_written;
uint64_t blocks_written;
};
@ -312,18 +303,19 @@ sun_copy_params(int fd, cubeb_stream * stream, cubeb_stream_params * params,
{
prinfo->channels = params->channels;
prinfo->sample_rate = params->rate;
prinfo->precision = 16;
#ifdef AUDIO_ENCODING_SLINEAR_LE
switch (params->format) {
case CUBEB_SAMPLE_S16LE:
prinfo->encoding = AUDIO_ENCODING_SLINEAR_LE;
prinfo->precision = 16;
break;
case CUBEB_SAMPLE_S16BE:
prinfo->encoding = AUDIO_ENCODING_SLINEAR_BE;
prinfo->precision = 16;
break;
case CUBEB_SAMPLE_FLOAT32NE:
stream->floating = 1;
prinfo->encoding = AUDIO_ENCODING_SLINEAR;
prinfo->precision = 32;
break;
default:
LOG("Unsupported format");
@ -333,10 +325,11 @@ sun_copy_params(int fd, cubeb_stream * stream, cubeb_stream_params * params,
switch (params->format) {
case CUBEB_SAMPLE_S16NE:
prinfo->encoding = AUDIO_ENCODING_LINEAR;
prinfo->precision = 16;
break;
case CUBEB_SAMPLE_FLOAT32NE:
stream->floating = 1;
prinfo->encoding = AUDIO_ENCODING_LINEAR;
prinfo->precision = 32;
break;
default:
LOG("Unsupported format");
@ -357,7 +350,7 @@ sun_stream_stop(cubeb_stream * s)
{
pthread_mutex_lock(&s->mutex);
if (s->running) {
s->running = 0;
s->running = false;
pthread_mutex_unlock(&s->mutex);
pthread_join(s->thread, NULL);
} else {
@ -369,55 +362,52 @@ sun_stream_stop(cubeb_stream * s)
static void
sun_stream_destroy(cubeb_stream * s)
{
pthread_mutex_destroy(&s->mutex);
sun_stream_stop(s);
if (s->play_fd != -1) {
close(s->play_fd);
pthread_mutex_destroy(&s->mutex);
if (s->play.fd != -1) {
close(s->play.fd);
}
if (s->record_fd != -1) {
close(s->record_fd);
if (s->record.fd != -1) {
close(s->record.fd);
}
free(s->f_play_buf);
free(s->f_record_buf);
free(s->play_buf);
free(s->record_buf);
free(s->play.buf);
free(s->record.buf);
free(s);
}
static void
sun_float_to_linear(float * in, int16_t * out,
unsigned channels, long frames, float vol)
sun_float_to_linear32(void * buf, unsigned sample_count, float vol)
{
unsigned i, sample_count = frames * channels;
float multiplier = vol * 0x8000;
float * in = buf;
int32_t * out = buf;
int32_t * tail = out + sample_count;
for (i = 0; i < sample_count; ++i) {
int32_t sample = lrintf(in[i] * multiplier);
if (sample < -0x8000) {
out[i] = -0x8000;
} else if (sample > 0x7fff) {
out[i] = 0x7fff;
} else {
out[i] = sample;
}
while (out < tail) {
float f = *(in++) * vol;
if (f < -1.0)
f = -1.0;
else if (f > 1.0)
f = 1.0;
*(out++) = f * (float)INT32_MAX;
}
}
static void
sun_linear_to_float(int16_t * in, float * out,
unsigned channels, long frames)
sun_linear32_to_float(void * buf, unsigned sample_count)
{
unsigned i, sample_count = frames * channels;
int32_t * in = buf;
float * out = buf;
float * tail = out + sample_count;
for (i = 0; i < sample_count; ++i) {
out[i] = (1.0 / 0x8000) * in[i];
while (out < tail) {
*(out++) = (1.0 / 0x80000000) * *(in++);
}
}
static void
sun_linear_set_vol(int16_t * buf, unsigned channels, long frames, float vol)
sun_linear16_set_vol(int16_t * buf, unsigned sample_count, float vol)
{
unsigned i, sample_count = frames * channels;
unsigned i;
int32_t multiplier = vol * 0x8000;
for (i = 0; i < sample_count; ++i) {
@ -445,41 +435,36 @@ sun_io_routine(void * arg)
break;
}
pthread_mutex_unlock(&s->mutex);
if (s->floating) {
if (s->record_fd != -1) {
sun_linear_to_float(s->record_buf, s->f_record_buf,
s->r_info.record.channels, SUN_BUFFER_FRAMES);
if (s->record.fd != -1 && s->record.floating) {
sun_linear32_to_float(s->record.buf,
s->record.info.record.channels * SUN_BUFFER_FRAMES);
}
to_write = s->data_cb(s, s->user_ptr,
s->f_record_buf, s->f_play_buf, SUN_BUFFER_FRAMES);
s->record.buf, s->play.buf, SUN_BUFFER_FRAMES);
if (to_write == CUBEB_ERROR) {
state = CUBEB_STATE_ERROR;
break;
}
if (s->play_fd != -1) {
if (s->play.fd != -1) {
float vol;
pthread_mutex_lock(&s->mutex);
sun_float_to_linear(s->f_play_buf, s->play_buf,
s->p_info.play.channels, to_write, s->volume);
vol = s->volume;
pthread_mutex_unlock(&s->mutex);
}
if (s->play.floating) {
sun_float_to_linear32(s->play.buf,
s->play.info.play.channels * to_write, vol);
} else {
to_write = s->data_cb(s, s->user_ptr,
s->record_buf, s->play_buf, SUN_BUFFER_FRAMES);
if (to_write == CUBEB_ERROR) {
state = CUBEB_STATE_ERROR;
break;
}
if (s->play_fd != -1) {
pthread_mutex_lock(&s->mutex);
sun_linear_set_vol(s->play_buf, s->p_info.play.channels, to_write, s->volume);
pthread_mutex_unlock(&s->mutex);
sun_linear16_set_vol(s->play.buf,
s->play.info.play.channels * to_write, vol);
}
}
if (to_write < SUN_BUFFER_FRAMES) {
drain = 1;
}
to_write = s->play_fd != -1 ? to_write : 0;
to_read = s->record_fd != -1 ? SUN_BUFFER_FRAMES : 0;
to_write = s->play.fd != -1 ? to_write : 0;
to_read = s->record.fd != -1 ? SUN_BUFFER_FRAMES : 0;
write_ofs = 0;
read_ofs = 0;
while (to_write > 0 || to_read > 0) {
@ -487,27 +472,27 @@ sun_io_routine(void * arg)
ssize_t n, frames;
if (to_write > 0) {
bytes = FRAMES_TO_BYTES(to_write, s->p_info.play.channels);
if ((n = write(s->play_fd, s->play_buf + write_ofs, bytes)) < 0) {
bytes = to_write * s->play.frame_size;
if ((n = write(s->play.fd, (uint8_t *)s->play.buf + write_ofs, bytes)) < 0) {
state = CUBEB_STATE_ERROR;
break;
}
frames = BYTES_TO_FRAMES(n, s->p_info.play.channels);
frames = n / s->play.frame_size;
pthread_mutex_lock(&s->mutex);
s->frames_written += frames;
pthread_mutex_unlock(&s->mutex);
to_write -= frames;
write_ofs += frames;
write_ofs += n;
}
if (to_read > 0) {
bytes = FRAMES_TO_BYTES(to_read, s->r_info.record.channels);
if ((n = read(s->record_fd, s->record_buf + read_ofs, bytes)) < 0) {
bytes = to_read * s->record.frame_size;
if ((n = read(s->record.fd, (uint8_t *)s->record.buf + read_ofs, bytes)) < 0) {
state = CUBEB_STATE_ERROR;
break;
}
frames = BYTES_TO_FRAMES(n, s->r_info.record.channels);
frames = n / s->record.frame_size;
to_read -= frames;
read_ofs += frames;
read_ofs += n;
}
}
if (drain && state != CUBEB_STATE_ERROR) {
@ -541,19 +526,19 @@ sun_stream_init(cubeb * context,
ret = CUBEB_ERROR;
goto error;
}
s->record_fd = -1;
s->play_fd = -1;
s->record.fd = -1;
s->play.fd = -1;
if (input_device != 0) {
snprintf(s->input_name, sizeof(s->input_name),
snprintf(s->record.name, sizeof(s->record.name),
"/dev/audio%zu", (uintptr_t)input_device - 1);
} else {
snprintf(s->input_name, sizeof(s->input_name), "%s", SUN_DEFAULT_DEVICE);
snprintf(s->record.name, sizeof(s->record.name), "%s", SUN_DEFAULT_DEVICE);
}
if (output_device != 0) {
snprintf(s->output_name, sizeof(s->output_name),
snprintf(s->play.name, sizeof(s->play.name),
"/dev/audio%zu", (uintptr_t)output_device - 1);
} else {
snprintf(s->output_name, sizeof(s->output_name), "%s", SUN_DEFAULT_DEVICE);
snprintf(s->play.name, sizeof(s->play.name), "%s", SUN_DEFAULT_DEVICE);
}
if (input_stream_params != NULL) {
if (input_stream_params->prefs & CUBEB_STREAM_PREF_LOOPBACK) {
@ -561,22 +546,23 @@ sun_stream_init(cubeb * context,
ret = CUBEB_ERROR_NOT_SUPPORTED;
goto error;
}
if (s->record_fd == -1) {
if ((s->record_fd = open(s->input_name, O_RDONLY)) == -1) {
LOG("Audio device cannot be opened as read-only");
if (s->record.fd == -1) {
if ((s->record.fd = open(s->record.name, O_RDONLY)) == -1) {
LOG("Audio device could not be opened as read-only");
ret = CUBEB_ERROR_DEVICE_UNAVAILABLE;
goto error;
}
}
AUDIO_INITINFO(&s->r_info);
AUDIO_INITINFO(&s->record.info);
#ifdef AUMODE_RECORD
s->r_info.mode = AUMODE_RECORD;
s->record.info.mode = AUMODE_RECORD;
#endif
if ((ret = sun_copy_params(s->record_fd, s, input_stream_params,
&s->r_info, &s->r_info.record)) != CUBEB_OK) {
if ((ret = sun_copy_params(s->record.fd, s, input_stream_params,
&s->record.info, &s->record.info.record)) != CUBEB_OK) {
LOG("Setting record params failed");
goto error;
}
s->record.floating = (input_stream_params->format == CUBEB_SAMPLE_FLOAT32NE);
}
if (output_stream_params != NULL) {
if (output_stream_params->prefs & CUBEB_STREAM_PREF_LOOPBACK) {
@ -584,22 +570,23 @@ sun_stream_init(cubeb * context,
ret = CUBEB_ERROR_NOT_SUPPORTED;
goto error;
}
if (s->play_fd == -1) {
if ((s->play_fd = open(s->output_name, O_WRONLY)) == -1) {
LOG("Audio device cannot be opened as write-only");
if (s->play.fd == -1) {
if ((s->play.fd = open(s->play.name, O_WRONLY)) == -1) {
LOG("Audio device could not be opened as write-only");
ret = CUBEB_ERROR_DEVICE_UNAVAILABLE;
goto error;
}
}
AUDIO_INITINFO(&s->p_info);
AUDIO_INITINFO(&s->play.info);
#ifdef AUMODE_PLAY
s->p_info.mode = AUMODE_PLAY;
s->play.info.mode = AUMODE_PLAY;
#endif
if ((ret = sun_copy_params(s->play_fd, s, output_stream_params,
&s->p_info, &s->p_info.play)) != CUBEB_OK) {
if ((ret = sun_copy_params(s->play.fd, s, output_stream_params,
&s->play.info, &s->play.info.play)) != CUBEB_OK) {
LOG("Setting play params failed");
goto error;
}
s->play.floating = (output_stream_params->format == CUBEB_SAMPLE_FLOAT32NE);
}
s->context = context;
s->volume = 1.0;
@ -610,28 +597,20 @@ sun_stream_init(cubeb * context,
LOG("Failed to create mutex");
goto error;
}
if (s->play_fd != -1 && (s->play_buf = calloc(SUN_BUFFER_FRAMES,
s->p_info.play.channels * sizeof(int16_t))) == NULL) {
s->play.frame_size = s->play.info.play.channels *
(s->play.info.play.precision / 8);
if (s->play.fd != -1 &&
(s->play.buf = calloc(SUN_BUFFER_FRAMES, s->play.frame_size)) == NULL) {
ret = CUBEB_ERROR;
goto error;
}
if (s->record_fd != -1 && (s->record_buf = calloc(SUN_BUFFER_FRAMES,
s->r_info.record.channels * sizeof(int16_t))) == NULL) {
s->record.frame_size = s->record.info.record.channels *
(s->record.info.record.precision / 8);
if (s->record.fd != -1 &&
(s->record.buf = calloc(SUN_BUFFER_FRAMES, s->record.frame_size)) == NULL) {
ret = CUBEB_ERROR;
goto error;
}
if (s->floating) {
if (s->play_fd != -1 && (s->f_play_buf = calloc(SUN_BUFFER_FRAMES,
s->p_info.play.channels * sizeof(float))) == NULL) {
ret = CUBEB_ERROR;
goto error;
}
if (s->record_fd != -1 && (s->f_record_buf = calloc(SUN_BUFFER_FRAMES,
s->r_info.record.channels * sizeof(float))) == NULL) {
ret = CUBEB_ERROR;
goto error;
}
}
*stream = s;
return CUBEB_OK;
error:
@ -644,7 +623,7 @@ error:
static int
sun_stream_start(cubeb_stream * s)
{
s->running = 1;
s->running = true;
if (pthread_create(&s->thread, NULL, sun_io_routine, s) != 0) {
LOG("Couldn't create thread");
return CUBEB_ERROR;
@ -658,12 +637,11 @@ sun_stream_get_position(cubeb_stream * s, uint64_t * position)
#ifdef AUDIO_GETOOFFS
struct audio_offset offset;
if (ioctl(s->play_fd, AUDIO_GETOOFFS, &offset) == -1) {
if (ioctl(s->play.fd, AUDIO_GETOOFFS, &offset) == -1) {
return CUBEB_ERROR;
}
s->blocks_written += offset.deltablks;
*position = BYTES_TO_FRAMES(s->blocks_written * s->p_info.blocksize,
s->p_info.play.channels);
*position = (s->blocks_written * s->play.info.blocksize) / s->play.frame_size;
return CUBEB_OK;
#else
pthread_mutex_lock(&s->mutex);
@ -674,22 +652,21 @@ sun_stream_get_position(cubeb_stream * s, uint64_t * position)
}
static int
sun_stream_get_latency(cubeb_stream * stream, uint32_t * latency)
sun_stream_get_latency(cubeb_stream * s, uint32_t * latency)
{
#ifdef AUDIO_GETBUFINFO
struct audio_info info;
if (ioctl(stream->play_fd, AUDIO_GETBUFINFO, &info) == -1) {
if (ioctl(s->play.fd, AUDIO_GETBUFINFO, &info) == -1) {
return CUBEB_ERROR;
}
*latency = BYTES_TO_FRAMES(info.play.seek + info.blocksize,
info.play.channels);
*latency = (info.play.seek + info.blocksize) / s->play.frame_size;
return CUBEB_OK;
#else
cubeb_stream_params params;
params.rate = stream->p_info.play.sample_rate;
params.rate = s->play.info.play.sample_rate;
return sun_get_min_latency(NULL, params, latency);
#endif
@ -711,10 +688,10 @@ sun_get_current_device(cubeb_stream * stream, cubeb_device ** const device)
if (*device == NULL) {
return CUBEB_ERROR;
}
(*device)->input_name = stream->record_fd != -1 ?
strdup(stream->input_name) : NULL;
(*device)->output_name = stream->play_fd != -1 ?
strdup(stream->output_name) : NULL;
(*device)->input_name = stream->record.fd != -1 ?
strdup(stream->record.name) : NULL;
(*device)->output_name = stream->play.fd != -1 ?
strdup(stream->play.name) : NULL;
return CUBEB_OK;
}
@ -744,7 +721,9 @@ static struct cubeb_ops const sun_ops = {
.stream_reset_default_device = NULL,
.stream_get_position = sun_stream_get_position,
.stream_get_latency = sun_stream_get_latency,
.stream_get_input_latency = NULL,
.stream_set_volume = sun_stream_set_volume,
.stream_set_name = NULL,
.stream_get_current_device = sun_get_current_device,
.stream_device_destroy = sun_stream_device_destroy,
.stream_register_device_changed_callback = NULL,

View File

@ -19,5 +19,6 @@ size_t cubeb_sample_size(cubeb_sample_format format)
default:
// should never happen as all cases are handled above.
assert(false);
return 0;
}
}

View File

@ -4,7 +4,7 @@
* This program is made available under an ISC-style license. See the
* accompanying file LICENSE for details.
*/
#define _WIN32_WINNT 0x0600
#define _WIN32_WINNT 0x0603
#define NOMINMAX
#include <initguid.h>
@ -89,6 +89,9 @@ DEFINE_PROPERTYKEY(PKEY_Device_InstanceId, 0x78c34fc8, 0x104a, 0x4aca, 0x9e
#endif
namespace {
const int64_t LATENCY_NOT_AVAILABLE_YET = -1;
struct com_heap_ptr_deleter {
void operator()(void * ptr) const noexcept {
CoTaskMemFree(ptr);
@ -190,6 +193,10 @@ int wasapi_stream_start(cubeb_stream * stm);
void close_wasapi_stream(cubeb_stream * stm);
int setup_wasapi_stream(cubeb_stream * stm);
ERole pref_to_role(cubeb_stream_prefs param);
int wasapi_create_device(cubeb * ctx, cubeb_device_info& ret, IMMDeviceEnumerator * enumerator, IMMDevice * dev);
void wasapi_destroy_device(cubeb_device_info * device_info);
static int wasapi_enumerate_devices(cubeb * context, cubeb_device_type type, cubeb_device_collection * out);
static int wasapi_device_collection_destroy(cubeb * ctx, cubeb_device_collection * collection);
static char const * wstr_to_utf8(wchar_t const * str);
static std::unique_ptr<wchar_t const []> utf8_to_wstr(char const * str);
@ -211,6 +218,7 @@ struct cubeb {
/* Collection changed for output (render) devices. */
cubeb_device_collection_changed_callback output_collection_changed_callback = nullptr;
void * output_collection_changed_user_ptr = nullptr;
UINT64 performance_counter_frequency;
};
class wasapi_endpoint_notification_client;
@ -241,9 +249,15 @@ struct cubeb_stream {
cubeb_stream_params output_stream_params = { CUBEB_SAMPLE_FLOAT32NE, 0, 0, CUBEB_LAYOUT_UNDEFINED, CUBEB_STREAM_PREF_NONE };
/* A MMDevice role for this stream: either communication or console here. */
ERole role;
/* True if this stream will transport voice-data. */
bool voice;
/* True if the input device of this stream is using bluetooth handsfree. */
bool input_bluetooth_handsfree;
/* The input and output device, or NULL for default. */
std::unique_ptr<const wchar_t[]> input_device;
std::unique_ptr<const wchar_t[]> output_device;
std::unique_ptr<const wchar_t[]> input_device_id;
std::unique_ptr<const wchar_t[]> output_device_id;
com_ptr<IMMDevice> input_device;
com_ptr<IMMDevice> output_device;
/* The latency initially requested for this stream, in frames. */
unsigned latency = 0;
cubeb_state_callback state_callback = nullptr;
@ -334,6 +348,9 @@ struct cubeb_stream {
std::atomic<std::atomic<bool>*> emergency_bailout { nullptr };
/* Synchronizes render thread start to ensure safe access to emergency_bailout. */
HANDLE thread_ready_event = 0;
/* This needs an active audio input stream to be known, and is updated in the
* first audio input callback. */
std::atomic<int64_t> input_latency_hns { LATENCY_NOT_AVAILABLE_YET };
};
class monitor_device_notifications {
@ -346,13 +363,14 @@ public:
~monitor_device_notifications()
{
SetEvent(shutdown);
WaitForSingleObject(thread, INFINITE);
SetEvent(begin_shutdown);
WaitForSingleObject(shutdown_complete, INFINITE);
CloseHandle(thread);
CloseHandle(input_changed);
CloseHandle(output_changed);
CloseHandle(shutdown);
CloseHandle(begin_shutdown);
CloseHandle(shutdown_complete);
}
void notify(EDataFlow flow)
@ -377,8 +395,9 @@ private:
thread_proc(LPVOID args)
{
XASSERT(args);
static_cast<monitor_device_notifications*>(args)
->notification_thread_loop();
auto mdn = static_cast<monitor_device_notifications*>(args);
mdn->notification_thread_loop();
SetEvent(mdn->shutdown_complete);
return 0;
}
@ -397,7 +416,7 @@ private:
HANDLE wait_array[3] = {
input_changed,
output_changed,
shutdown,
begin_shutdown,
};
while (true) {
@ -435,9 +454,15 @@ private:
return;
}
shutdown = CreateEvent(nullptr, 0, 0, nullptr);
if (!shutdown) {
LOG("Failed to create shutdown event.");
begin_shutdown = CreateEvent(nullptr, 0, 0, nullptr);
if (!begin_shutdown) {
LOG("Failed to create begin_shutdown event.");
return;
}
shutdown_complete = CreateEvent(nullptr, 0, 0, nullptr);
if (!shutdown_complete) {
LOG("Failed to create shutdown_complete event.");
return;
}
@ -456,7 +481,8 @@ private:
HANDLE thread = INVALID_HANDLE_VALUE;
HANDLE output_changed = INVALID_HANDLE_VALUE;
HANDLE input_changed = INVALID_HANDLE_VALUE;
HANDLE shutdown = INVALID_HANDLE_VALUE;
HANDLE begin_shutdown = INVALID_HANDLE_VALUE;
HANDLE shutdown_complete = INVALID_HANDLE_VALUE;
cubeb * cubeb_context = nullptr;
};
@ -685,8 +711,9 @@ intern_device_id(cubeb * ctx, wchar_t const * id)
XASSERT(id);
char const * tmp = wstr_to_utf8(id);
if (!tmp)
if (!tmp) {
return nullptr;
}
char const * interned = cubeb_strings_intern(ctx->device_ids, tmp);
@ -757,6 +784,12 @@ frames_to_hns(cubeb_stream * stm, uint32_t frames)
return std::ceil(frames * 10000000.0 / get_rate(stm));
}
REFERENCE_TIME
frames_to_hns(uint32_t rate, uint32_t frames)
{
return std::ceil(frames * 10000000.0 / rate);
}
/* This returns the size of a frame in the stream, before the eventual upmix
occurs. */
static size_t
@ -847,6 +880,7 @@ bool get_input_buffer(cubeb_stream * stm)
BYTE * input_packet = NULL;
DWORD flags;
UINT64 dev_pos;
UINT64 pc_position;
UINT32 next;
/* Get input packets until we have captured enough frames, and put them in a
* contiguous buffer. */
@ -876,13 +910,25 @@ bool get_input_buffer(cubeb_stream * stm)
&frames,
&flags,
&dev_pos,
NULL);
&pc_position);
if (FAILED(hr)) {
LOG("GetBuffer failed for capture: %lx", hr);
return false;
}
XASSERT(frames == next);
if (stm->context->performance_counter_frequency) {
LARGE_INTEGER now;
UINT64 now_hns;
// See https://docs.microsoft.com/en-us/windows/win32/api/audioclient/nf-audioclient-iaudiocaptureclient-getbuffer, section "Remarks".
QueryPerformanceCounter(&now);
now_hns = 10000000 * now.QuadPart / stm->context->performance_counter_frequency;
if (now_hns >= pc_position) {
stm->input_latency_hns = now_hns - pc_position;
}
}
UINT32 input_stream_samples = frames * stm->input_stream_params.channels;
// We do not explicitly handle the AUDCLNT_BUFFERFLAGS_DATA_DISCONTINUITY
// flag. There a two primary (non exhaustive) scenarios we anticipate this
@ -1008,9 +1054,6 @@ refill_callback_duplex(cubeb_stream * stm)
}
input_frames = stm->linear_input_buffer->length() / stm->input_stream_params.channels;
if (!input_frames) {
return true;
}
rv = get_output_buffer(stm, output_buffer, output_frames);
if (!rv) {
@ -1303,17 +1346,11 @@ void wasapi_destroy(cubeb * context);
HRESULT register_notification_client(cubeb_stream * stm)
{
HRESULT hr = CoCreateInstance(__uuidof(MMDeviceEnumerator),
NULL, CLSCTX_INPROC_SERVER,
IID_PPV_ARGS(stm->device_enumerator.receive()));
if (FAILED(hr)) {
LOG("Could not get device enumerator: %lx", hr);
return hr;
}
assert(stm->device_enumerator);
stm->notification_client.reset(new wasapi_endpoint_notification_client(stm->reconfigure_event, stm->role));
hr = stm->device_enumerator->RegisterEndpointNotificationCallback(stm->notification_client.get());
HRESULT hr = stm->device_enumerator->RegisterEndpointNotificationCallback(stm->notification_client.get());
if (FAILED(hr)) {
LOG("Could not register endpoint notification callback: %lx", hr);
stm->notification_client = nullptr;
@ -1341,7 +1378,6 @@ HRESULT unregister_notification_client(cubeb_stream * stm)
}
stm->notification_client = nullptr;
stm->device_enumerator = nullptr;
return S_OK;
}
@ -1450,8 +1486,7 @@ current_stream_delay(cubeb_stream * stm)
double cur_pos = static_cast<double>(pos) / freq;
double max_pos = static_cast<double>(stm->frames_written) / stm->output_mix_params.rate;
double delay = max_pos - cur_pos;
XASSERT(delay >= 0);
double delay = std::max(max_pos - cur_pos, 0.0);
return delay;
}
@ -1518,6 +1553,14 @@ int wasapi_init(cubeb ** context, char const * context_name)
return CUBEB_ERROR;
}
LARGE_INTEGER frequency;
if (QueryPerformanceFrequency(&frequency)) {
ctx->performance_counter_frequency = frequency.QuadPart;
} else {
LOG("Failed getting performance counter frequency, latency reporting will be inacurate");
ctx->performance_counter_frequency = 0;
}
*context = ctx;
return CUBEB_OK;
@ -1741,17 +1784,18 @@ handle_channel_layout(cubeb_stream * stm, EDataFlow direction, com_heap_ptr<WAV
waveformatex_update_derived_properties(mix_format.get());
/* Check if wasapi will accept our channel layout request. */
WAVEFORMATEX * closest;
WAVEFORMATEX * tmp = nullptr;
HRESULT hr = audio_client->IsFormatSupported(AUDCLNT_SHAREMODE_SHARED,
mix_format.get(),
&closest);
&tmp);
com_heap_ptr<WAVEFORMATEX> closest(tmp);
if (hr == S_FALSE) {
/* Channel layout not supported, but WASAPI gives us a suggestion. Use it,
and handle the eventual upmix/downmix ourselves. Ignore the subformat of
the suggestion, since it seems to always be IEEE_FLOAT. */
LOG("Using WASAPI suggested format: channels: %d", closest->nChannels);
XASSERT(closest->wFormatTag == WAVE_FORMAT_EXTENSIBLE);
WAVEFORMATEXTENSIBLE * closest_pcm = reinterpret_cast<WAVEFORMATEXTENSIBLE *>(closest);
WAVEFORMATEXTENSIBLE * closest_pcm = reinterpret_cast<WAVEFORMATEXTENSIBLE *>(closest.get());
format_pcm->dwChannelMask = closest_pcm->dwChannelMask;
mix_format->nChannels = closest->nChannels;
waveformatex_update_derived_properties(mix_format.get());
@ -1768,6 +1812,28 @@ handle_channel_layout(cubeb_stream * stm, EDataFlow direction, com_heap_ptr<WAV
}
}
static bool
initialize_iaudioclient2(com_ptr<IAudioClient> & audio_client)
{
com_ptr<IAudioClient2> audio_client2;
audio_client->QueryInterface<IAudioClient2>(audio_client2.receive());
if (!audio_client2) {
LOG("Could not get IAudioClient2 interface, not setting AUDCLNT_STREAMOPTIONS_RAW.");
return CUBEB_OK;
}
AudioClientProperties properties = { 0 };
properties.cbSize = sizeof(AudioClientProperties);
#ifndef __MINGW32__
properties.Options |= AUDCLNT_STREAMOPTIONS_RAW;
#endif
HRESULT hr = audio_client2->SetClientProperties(&properties);
if (FAILED(hr)) {
LOG("IAudioClient2::SetClientProperties error: %lx", GetLastError());
return CUBEB_ERROR;
}
return CUBEB_OK;
}
static bool
initialize_iaudioclient3(com_ptr<IAudioClient> & audio_client,
cubeb_stream * stm,
@ -1791,7 +1857,7 @@ initialize_iaudioclient3(com_ptr<IAudioClient> & audio_client,
// IAudioClient3 doesn't support AUDCLNT_STREAMFLAGS_NOPERSIST, and will return
// AUDCLNT_E_INVALID_STREAM_FLAG. This is undocumented.
flags = flags ^ AUDCLNT_STREAMFLAGS_NOPERSIST;
flags = flags & ~AUDCLNT_STREAMFLAGS_NOPERSIST;
// Some people have reported glitches with capture streams:
// http://blog.nirbheek.in/2018/03/low-latency-audio-on-windows-with.html
@ -1883,9 +1949,9 @@ int setup_wasapi_stream_one_side(cubeb_stream * stm,
uint32_t * buffer_frame_count,
HANDLE & event,
T & render_or_capture_client,
cubeb_stream_params * mix_params)
cubeb_stream_params * mix_params,
com_ptr<IMMDevice>& device)
{
com_ptr<IMMDevice> device;
HRESULT hr;
bool is_loopback = stream_params->prefs & CUBEB_STREAM_PREF_LOOPBACK;
if (is_loopback && direction != eCapture) {
@ -1921,14 +1987,18 @@ int setup_wasapi_stream_one_side(cubeb_stream * stm,
/* Get a client. We will get all other interfaces we need from
* this pointer. */
#if 0 // See https://bugzilla.mozilla.org/show_bug.cgi?id=1590902
hr = device->Activate(__uuidof(IAudioClient3),
CLSCTX_INPROC_SERVER,
NULL, audio_client.receive_vpp());
if (hr == E_NOINTERFACE) {
#endif
hr = device->Activate(__uuidof(IAudioClient),
CLSCTX_INPROC_SERVER,
NULL, audio_client.receive_vpp());
#if 0
}
#endif
if (FAILED(hr)) {
LOG("Could not activate the device to get an audio"
@ -1984,7 +2054,13 @@ int setup_wasapi_stream_one_side(cubeb_stream * stm,
mix_params->format, mix_params->rate, mix_params->channels,
mix_params->layout);
DWORD flags = AUDCLNT_STREAMFLAGS_NOPERSIST;
DWORD flags = 0;
bool is_persist = stream_params->prefs & CUBEB_STREAM_PREF_PERSIST;
if (!is_persist) {
flags |= AUDCLNT_STREAMFLAGS_NOPERSIST;
}
// Check if a loopback device should be requested. Note that event callbacks
// do not work with loopback devices, so only request these if not looping.
@ -1994,16 +2070,67 @@ int 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;
uint32_t latency_frames = stm->latency;
cubeb_device_info device_info;
int rv = wasapi_create_device(stm->context, device_info, stm->device_enumerator.get(), device.get());
if (rv == CUBEB_OK) {
const char* HANDSFREE_TAG = "BTHHFEENUM";
size_t len = sizeof(HANDSFREE_TAG);
if (direction == eCapture && strncmp(device_info.group_id, HANDSFREE_TAG, len) == 0) {
// Rather high-latency to prevent constant under-runs in this particular
// case of an input device using bluetooth handsfree.
uint32_t default_period_frames = hns_to_frames(device_info.default_rate, default_period);
latency_frames = default_period_frames * 4;
stm->input_bluetooth_handsfree = true;
LOG("Input is a bluetooth device in handsfree, latency increased to %u frames from a default of %u", latency_frames, default_period_frames);
} else {
uint32_t minimum_period_frames = hns_to_frames(device_info.default_rate, minimum_period);
latency_frames = std::max(latency_frames, minimum_period_frames);
stm->input_bluetooth_handsfree = false;
LOG("Input is a not bluetooth handsfree, latency %s to %u frames (minimum %u)", latency_frames < minimum_period_frames ? "increased" : "set", latency_frames, minimum_period_frames);
}
latency_hns = frames_to_hns(device_info.default_rate, latency_frames);
wasapi_destroy_device(&device_info);
} else {
stm->input_bluetooth_handsfree = false;
latency_hns = frames_to_hns(mix_params->rate, latency_frames);
LOG("Could not get cubeb_device_info.");
}
if (stream_params->prefs & CUBEB_STREAM_PREF_RAW) {
if (initialize_iaudioclient2(audio_client) != CUBEB_OK) {
LOG("Can't initialize an IAudioClient2, error: %lx", GetLastError());
// This is not fatal.
}
}
#if 0 // See https://bugzilla.mozilla.org/show_bug.cgi?id=1590902
if (initialize_iaudioclient3(audio_client, stm, mix_format, flags, direction)) {
LOG("Initialized with IAudioClient3");
} else {
#endif
hr = audio_client->Initialize(AUDCLNT_SHAREMODE_SHARED,
flags,
frames_to_hns(stm, stm->latency),
latency_hns,
0,
mix_format.get(),
NULL);
#if 0
}
#endif
if (FAILED(hr)) {
LOG("Unable to initialize audio client for %s: %lx.", DIRECTION_NAME, hr);
return CUBEB_ERROR;
@ -2037,6 +2164,54 @@ int setup_wasapi_stream_one_side(cubeb_stream * stm,
#undef DIRECTION_NAME
void wasapi_find_matching_output_device(cubeb_stream * stm) {
HRESULT hr;
cubeb_device_info * input_device;
cubeb_device_collection collection;
// 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;
}
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;
}
com_heap_ptr<wchar_t> device_id(tmp);
cubeb_devid input_device_id = intern_device_id(stm->context, device_id.get());
if (!input_device_id) {
return;
}
int rv = wasapi_enumerate_devices(stm->context, (cubeb_device_type)(CUBEB_DEVICE_TYPE_INPUT|CUBEB_DEVICE_TYPE_OUTPUT), &collection);
// Find the input device, and then find the output device with the same group
// id and the same rate.
for (uint32_t i = 0; i < collection.count; i++) {
cubeb_device_info dev = collection.device[i];
if (dev.devid == input_device_id) {
input_device = &dev;
break;
}
}
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);
stm->output_device_id = utf8_to_wstr(reinterpret_cast<char const *>(dev.devid));
}
}
wasapi_device_collection_destroy(stm->context, &collection);
}
int setup_wasapi_stream(cubeb_stream * stm)
{
int rv;
@ -2046,17 +2221,18 @@ int setup_wasapi_stream(cubeb_stream * stm)
XASSERT((!stm->output_client || !stm->input_client) && "WASAPI stream already setup, close it first.");
if (has_input(stm)) {
LOG("(%p) Setup capture: device=%p", stm, stm->input_device.get());
LOG("(%p) Setup capture: device=%p", stm, stm->input_device_id.get());
rv = setup_wasapi_stream_one_side(stm,
&stm->input_stream_params,
stm->input_device.get(),
stm->input_device_id.get(),
eCapture,
__uuidof(IAudioCaptureClient),
stm->input_client,
&stm->input_buffer_frame_count,
stm->input_available_event,
stm->capture_client,
&stm->input_mix_params);
&stm->input_mix_params,
stm->input_device);
if (rv != CUBEB_OK) {
LOG("Failure to open the input side.");
return rv;
@ -2075,6 +2251,14 @@ int setup_wasapi_stream(cubeb_stream * stm)
stm->linear_input_buffer->push_silence(stm->input_buffer_frame_count *
stm->input_stream_params.channels *
silent_buffer_count);
// If this is a bluetooth device, and the output device is the default
// 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 we don't have an output device but are requesting a loopback device,
@ -2085,31 +2269,32 @@ int setup_wasapi_stream(cubeb_stream * stm)
stm->output_stream_params.rate = stm->input_stream_params.rate;
stm->output_stream_params.channels = stm->input_stream_params.channels;
stm->output_stream_params.layout = stm->input_stream_params.layout;
if (stm->input_device) {
size_t len = wcslen(stm->input_device.get());
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.get(), len) != 0) {
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.");
return CUBEB_ERROR;
}
stm->output_device = move(tmp);
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.get());
LOG("(%p) Setup render: device=%p", stm, stm->output_device_id.get());
rv = setup_wasapi_stream_one_side(stm,
&stm->output_stream_params,
stm->output_device.get(),
stm->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_mix_params,
stm->output_device);
if (rv != CUBEB_OK) {
LOG("Failure to open the output side.");
return rv;
@ -2168,7 +2353,7 @@ int setup_wasapi_stream(cubeb_stream * stm)
target_sample_rate,
stm->data_callback,
stm->user_ptr,
CUBEB_RESAMPLER_QUALITY_DESKTOP));
stm->voice ? CUBEB_RESAMPLER_QUALITY_VOIP : CUBEB_RESAMPLER_QUALITY_DESKTOP));
if (!stm->resampler) {
LOG("Could not get a resampler");
return CUBEB_ERROR;
@ -2255,21 +2440,31 @@ wasapi_stream_init(cubeb * context, cubeb_stream ** stream,
stm->data_callback = data_callback;
stm->state_callback = state_callback;
stm->user_ptr = user_ptr;
if (stm->output_stream_params.prefs & CUBEB_STREAM_PREF_VOICE ||
stm->input_stream_params.prefs & CUBEB_STREAM_PREF_VOICE) {
stm->role = eCommunications;
} else {
stm->role = eConsole;
stm->input_bluetooth_handsfree = false;
HRESULT hr = CoCreateInstance(__uuidof(MMDeviceEnumerator),
NULL, CLSCTX_INPROC_SERVER,
IID_PPV_ARGS(stm->device_enumerator.receive()));
if (FAILED(hr)) {
LOG("Could not get device enumerator: %lx", hr);
return hr;
}
if (input_stream_params) {
stm->input_stream_params = *input_stream_params;
stm->input_device = utf8_to_wstr(reinterpret_cast<char const *>(input_device));
stm->input_device_id = utf8_to_wstr(reinterpret_cast<char const *>(input_device));
}
if (output_stream_params) {
stm->output_stream_params = *output_stream_params;
stm->output_device = utf8_to_wstr(reinterpret_cast<char const *>(output_device));
stm->output_device_id = utf8_to_wstr(reinterpret_cast<char const *>(output_device));
}
if (stm->output_stream_params.prefs & CUBEB_STREAM_PREF_VOICE ||
stm->input_stream_params.prefs & CUBEB_STREAM_PREF_VOICE) {
stm->voice = true;
} else {
stm->voice = false;
}
switch (output_stream_params ? output_stream_params->format : input_stream_params->format) {
@ -2349,6 +2544,9 @@ void close_wasapi_stream(cubeb_stream * stm)
stm->input_client = nullptr;
stm->capture_client = nullptr;
stm->output_device = nullptr;
stm->input_device = nullptr;
stm->audio_stream_volume = nullptr;
stm->audio_clock = nullptr;
@ -2386,6 +2584,8 @@ void wasapi_stream_destroy(cubeb_stream * stm)
// must be destroyed in wasapi_stream_destroy.
stm->linear_input_buffer.reset();
stm->device_enumerator = nullptr;
{
auto_lock lock(stm->stream_reset_lock);
close_wasapi_stream(stm);
@ -2584,15 +2784,49 @@ int wasapi_stream_get_latency(cubeb_stream * stm, uint32_t * latency)
/* The GetStreamLatency method only works if the
AudioClient has been initialized. */
if (!stm->output_client) {
LOG("get_latency: No output_client.");
return CUBEB_ERROR;
}
REFERENCE_TIME latency_hns;
HRESULT hr = stm->output_client->GetStreamLatency(&latency_hns);
if (FAILED(hr)) {
LOG("GetStreamLatency failed %lx.", hr);
return CUBEB_ERROR;
}
// This happens on windows 10: no error, but always 0 for latency.
if (latency_hns == 0) {
LOG("GetStreamLatency returned 0, using workaround.");
double delay_s = current_stream_delay(stm);
// convert to sample-frames
*latency = delay_s * stm->output_stream_params.rate;
} else {
*latency = hns_to_frames(stm, latency_hns);
}
LOG("Output latency %u frames.", *latency);
return CUBEB_OK;
}
int wasapi_stream_get_input_latency(cubeb_stream * stm, uint32_t * latency)
{
XASSERT(stm && latency);
if (!has_input(stm)) {
LOG("Input latency queried on an output-only stream.");
return CUBEB_ERROR;
}
auto_lock lock(stm->stream_reset_lock);
if (stm->input_latency_hns == LATENCY_NOT_AVAILABLE_YET) {
LOG("Input latency not available yet.");
return CUBEB_ERROR;
}
*latency = hns_to_frames(stm, stm->input_latency_hns);
return CUBEB_OK;
}
@ -2680,6 +2914,8 @@ wasapi_is_default_device(EDataFlow flow, ERole role, LPCWSTR device_id,
return ret;
}
/* `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)
{
@ -2692,6 +2928,10 @@ wasapi_create_device(cubeb * ctx, cubeb_device_info& ret, IMMDeviceEnumerator *
REFERENCE_TIME def_period, min_period;
HRESULT hr;
// 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);
struct prop_variant : public PROPVARIANT {
prop_variant() { PropVariantInit(this); }
~prop_variant() { PropVariantClear(this); }
@ -2700,57 +2940,97 @@ wasapi_create_device(cubeb * ctx, cubeb_device_info& ret, IMMDeviceEnumerator *
};
hr = dev->QueryInterface(IID_PPV_ARGS(endpoint.receive()));
if (FAILED(hr)) return CUBEB_ERROR;
if (FAILED(hr)) {
wasapi_destroy_device(&ret);
return CUBEB_ERROR;
}
hr = endpoint->GetDataFlow(&flow);
if (FAILED(hr)) return CUBEB_ERROR;
if (FAILED(hr)) {
wasapi_destroy_device(&ret);
return CUBEB_ERROR;
}
wchar_t * tmp = nullptr;
hr = dev->GetId(&tmp);
if (FAILED(hr)) return CUBEB_ERROR;
if (FAILED(hr)) {
wasapi_destroy_device(&ret);
return CUBEB_ERROR;
}
com_heap_ptr<wchar_t> device_id(tmp);
char const * device_id_intern = intern_device_id(ctx, device_id.get());
if (!device_id_intern) {
wasapi_destroy_device(&ret);
return CUBEB_ERROR;
}
hr = dev->OpenPropertyStore(STGM_READ, propstore.receive());
if (FAILED(hr)) return CUBEB_ERROR;
if (FAILED(hr)) {
wasapi_destroy_device(&ret);
return CUBEB_ERROR;
}
hr = dev->GetState(&state);
if (FAILED(hr)) return CUBEB_ERROR;
if (FAILED(hr)) {
wasapi_destroy_device(&ret);
return CUBEB_ERROR;
}
ret.device_id = device_id_intern;
ret.devid = reinterpret_cast<cubeb_devid>(ret.device_id);
prop_variant namevar;
hr = propstore->GetValue(PKEY_Device_FriendlyName, &namevar);
if (SUCCEEDED(hr))
if (SUCCEEDED(hr) && namevar.vt == VT_LPWSTR) {
ret.friendly_name = wstr_to_utf8(namevar.pwszVal);
}
if (!ret.friendly_name) {
// This is not fatal, but a valid string is expected in all cases.
char* empty = new char[1];
empty[0] = '\0';
ret.friendly_name = empty;
}
devnode = wasapi_get_device_node(enumerator, dev);
if (devnode) {
com_ptr<IPropertyStore> ps;
hr = devnode->OpenPropertyStore(STGM_READ, ps.receive());
if (FAILED(hr)) return CUBEB_ERROR;
if (FAILED(hr)) {
wasapi_destroy_device(&ret);
return CUBEB_ERROR;
}
prop_variant instancevar;
hr = ps->GetValue(PKEY_Device_InstanceId, &instancevar);
if (SUCCEEDED(hr)) {
if (SUCCEEDED(hr) && instancevar.vt == VT_LPWSTR) {
ret.group_id = wstr_to_utf8(instancevar.pwszVal);
}
}
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);
if (wasapi_is_default_device(flow, eCommunications, device_id.get(), enumerator))
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 (!ret.group_id) {
// This is not fatal, but a valid string is expected in all cases.
char* empty = new char[1];
empty[0] = '\0';
ret.group_id = empty;
}
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);
}
if (wasapi_is_default_device(flow, eCommunications, device_id.get(), enumerator)) {
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;
} 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;
@ -2793,9 +3073,18 @@ wasapi_create_device(cubeb * ctx, cubeb_device_info& ret, IMMDeviceEnumerator *
ret.latency_hi = 0;
}
XASSERT(ret.friendly_name && ret.group_id);
return CUBEB_OK;
}
void
wasapi_destroy_device(cubeb_device_info * device)
{
delete [] device->friendly_name;
delete [] device->group_id;
}
static int
wasapi_enumerate_devices(cubeb * context, cubeb_device_type type,
cubeb_device_collection * out)
@ -2859,8 +3148,7 @@ wasapi_device_collection_destroy(cubeb * /*ctx*/, cubeb_device_collection * coll
for (size_t n = 0; n < collection->count; n++) {
cubeb_device_info& dev = collection->device[n];
delete [] dev.friendly_name;
delete [] dev.group_id;
wasapi_destroy_device(&dev);
}
delete [] collection->device;
@ -2956,7 +3244,9 @@ cubeb_ops const wasapi_ops = {
/*.stream_reset_default_device =*/ wasapi_stream_reset_default_device,
/*.stream_get_position =*/ wasapi_stream_get_position,
/*.stream_get_latency =*/ wasapi_stream_get_latency,
/*.stream_get_input_latency =*/ wasapi_stream_get_input_latency,
/*.stream_set_volume =*/ wasapi_stream_set_volume,
/*.stream_set_name =*/ NULL,
/*.stream_get_current_device =*/ NULL,
/*.stream_device_destroy =*/ NULL,
/*.stream_register_device_changed_callback =*/ NULL,

View File

@ -1059,7 +1059,9 @@ static struct cubeb_ops const winmm_ops = {
/*.stream_reset_default_device =*/ NULL,
/*.stream_get_position =*/ winmm_stream_get_position,
/*.stream_get_latency = */ winmm_stream_get_latency,
/*.stream_get_input_latency = */ NULL,
/*.stream_set_volume =*/ winmm_stream_set_volume,
/*.stream_set_name =*/ NULL,
/*.stream_get_current_device =*/ NULL,
/*.stream_device_destroy =*/ NULL,
/*.stream_register_device_changed_callback=*/ NULL,