dep/cubeb: Update to 1d66483
This commit is contained in:
parent
1b618b8c46
commit
045866506f
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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>>
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
Loading…
Reference in New Issue