Merge pull request #1164 from ggrtk/update-cubeb

dep/cubeb: Update to 70fadbf
This commit is contained in:
Connor McLaughlin 2020-12-08 01:41:40 +10:00 committed by GitHub
commit 986e52990e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 1687 additions and 123 deletions

View file

@ -22,7 +22,10 @@ endif()
set(CMAKE_CXX_WARNING_LEVEL 4) set(CMAKE_CXX_WARNING_LEVEL 4)
if(NOT MSVC) if(NOT MSVC)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra -Wno-unused-parameter") set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra -Wno-unused-parameter")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Wno-unused-parameter") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Wno-unused-parameter -fno-exceptions -fno-rtti")
else()
string(REPLACE "/GR" "" CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS}) # Disable RTTI
string(REPLACE "/EHsc" "" CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS}) # Disable Exceptions
endif() endif()
add_library(cubeb add_library(cubeb
@ -122,6 +125,26 @@ if(HAVE_SYS_SOUNDCARD_H)
endif() endif()
endif() endif()
check_include_files(aaudio/AAudio.h USE_AAUDIO)
if(USE_AAUDIO)
target_sources(cubeb PRIVATE
src/cubeb_aaudio.cpp)
target_compile_definitions(cubeb PRIVATE USE_AAUDIO)
# set this definition to enable low latency mode. Possibly bad for battery
target_compile_definitions(cubeb PRIVATE CUBEB_AAUDIO_LOW_LATENCY)
# set this definition to enable power saving mode. Possibly resulting
# in high latency
# target_compile_definitions(cubeb PRIVATE CUBEB_AAUDIO_LOW_POWER_SAVING)
# set this mode to make the backend use an exclusive stream.
# will decrease latency.
# target_compile_definitions(cubeb PRIVATE CUBEB_AAUDIO_EXCLUSIVE_STREAM)
target_link_libraries(cubeb PRIVATE ${CMAKE_DL_LIBS})
endif()
check_include_files(android/log.h USE_AUDIOTRACK) check_include_files(android/log.h USE_AUDIOTRACK)
if(USE_AUDIOTRACK) if(USE_AUDIOTRACK)
target_sources(cubeb PRIVATE target_sources(cubeb PRIVATE

View file

@ -509,7 +509,9 @@ CUBEB_EXPORT void cubeb_destroy(cubeb * context);
cubeb_devid allows the stream to follow that device type's cubeb_devid allows the stream to follow that device type's
OS default. OS default.
@param output_stream_params Parameters for the output side of the stream, or @param output_stream_params Parameters for the output side of the stream, or
NULL if this stream is input only. NULL if this stream is input only. When input
and output stream parameters are supplied, their
rate has to be the same.
@param latency_frames Stream latency in frames. Valid range @param latency_frames Stream latency in frames. Valid range
is [1, 96000]. is [1, 96000].
@param data_callback Will be called to preroll data before playback is @param data_callback Will be called to preroll data before playback is

View file

@ -63,6 +63,9 @@ int opensl_init(cubeb ** context, char const * context_name);
#if defined(USE_OSS) #if defined(USE_OSS)
int oss_init(cubeb ** context, char const * context_name); int oss_init(cubeb ** context, char const * context_name);
#endif #endif
#if defined(USE_AAUDIO)
int aaudio_init(cubeb ** context, char const * context_name);
#endif
#if defined(USE_AUDIOTRACK) #if defined(USE_AUDIOTRACK)
int audiotrack_init(cubeb ** context, char const * context_name); int audiotrack_init(cubeb ** context, char const * context_name);
#endif #endif
@ -172,6 +175,10 @@ cubeb_init(cubeb ** context, char const * context_name, char const * backend_nam
} else if (!strcmp(backend_name, "oss")) { } else if (!strcmp(backend_name, "oss")) {
#if defined(USE_OSS) #if defined(USE_OSS)
init_oneshot = oss_init; init_oneshot = oss_init;
#endif
} else if (!strcmp(backend_name, "aaudio")) {
#if defined(USE_AAUDIO)
init_oneshot = aaudio_init;
#endif #endif
} else if (!strcmp(backend_name, "audiotrack")) { } else if (!strcmp(backend_name, "audiotrack")) {
#if defined(USE_AUDIOTRACK) #if defined(USE_AUDIOTRACK)
@ -227,6 +234,11 @@ cubeb_init(cubeb ** context, char const * context_name, char const * backend_nam
#endif #endif
#if defined(USE_OPENSL) #if defined(USE_OPENSL)
opensl_init, opensl_init,
#endif
// TODO: should probably be preferred over OpenSLES when available.
// Initialization will fail on old android devices.
#if defined(USE_AAUDIO)
aaudio_init,
#endif #endif
#if defined(USE_AUDIOTRACK) #if defined(USE_AUDIOTRACK)
audiotrack_init, audiotrack_init,

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,17 @@
#ifndef CUBEB_ANDROID_H
#define CUBEB_ANDROID_H
#ifdef __cplusplus
extern "C" {
#endif
// If the latency requested is above this threshold, this stream is considered
// intended for playback (vs. real-time). Tell Android it should favor saving
// power over performance or latency.
// This is around 100ms at 44100 or 48000
const uint16_t POWERSAVE_LATENCY_FRAMES_THRESHOLD = 4000;
#ifdef __cplusplus
};
#endif
#endif // CUBEB_ANDROID_H

View file

@ -46,6 +46,7 @@ void cubeb_async_log_reset_threads();
} while(0) } while(0)
/* Asynchronous verbose logging, to log in real-time callbacks. */ /* Asynchronous verbose logging, to log in real-time callbacks. */
/* Should not be used on android due to the use of global/static variables. */
#define ALOGV(fmt, ...) \ #define ALOGV(fmt, ...) \
do { \ do { \
cubeb_async_log(fmt, ##__VA_ARGS__); \ cubeb_async_log(fmt, ##__VA_ARGS__); \

View file

@ -27,6 +27,7 @@
#include "cubeb-sles.h" #include "cubeb-sles.h"
#include "cubeb_array_queue.h" #include "cubeb_array_queue.h"
#include "android/cubeb-output-latency.h" #include "android/cubeb-output-latency.h"
#include "cubeb_android.h"
#if defined(__ANDROID__) #if defined(__ANDROID__)
#ifdef LOG #ifdef LOG
@ -65,11 +66,6 @@
#define DEFAULT_SAMPLE_RATE 48000 #define DEFAULT_SAMPLE_RATE 48000
#define DEFAULT_NUM_OF_FRAMES 480 #define DEFAULT_NUM_OF_FRAMES 480
// If the latency requested is above this threshold, this stream is considered
// intended for playback (vs. real-time). Tell Android it should favor saving
// power over performance or latency.
// This is around 100ms at 44100 or 48000
#define POWERSAVE_LATENCY_FRAMES_THRESHOLD 4000
static struct cubeb_ops const opensl_ops; static struct cubeb_ops const opensl_ops;
@ -1702,7 +1698,7 @@ opensl_stream_get_latency(cubeb_stream * stm, uint32_t * latency)
assert(latency); assert(latency);
uint32_t stream_latency_frames = uint32_t stream_latency_frames =
stm->user_output_rate * (stm->output_latency_ms / 1000); stm->user_output_rate * stm->output_latency_ms / 1000;
return stream_latency_frames + cubeb_resampler_latency(stm->resampler); return stream_latency_frames + cubeb_resampler_latency(stm->resampler);
} }

View file

@ -741,6 +741,46 @@ oss_linear16_set_vol(int16_t * buf, unsigned sample_count, float vol)
} }
} }
static int
oss_get_rec_frames(cubeb_stream * s, unsigned int nframes)
{
size_t rem = nframes * s->record.frame_size;
size_t read_ofs = 0;
while (rem > 0) {
ssize_t n;
if ((n = read(s->record.fd, (uint8_t *)s->record.buf + read_ofs, rem)) < 0) {
if (errno == EINTR)
continue;
return CUBEB_ERROR;
}
read_ofs += n;
rem -= n;
}
return 0;
}
static int
oss_put_play_frames(cubeb_stream * s, unsigned int nframes)
{
size_t rem = nframes * s->play.frame_size;
size_t write_ofs = 0;
while (rem > 0) {
ssize_t n;
if ((n = write(s->play.fd, (uint8_t *)s->play.buf + write_ofs, rem)) < 0) {
if (errno == EINTR)
continue;
return CUBEB_ERROR;
}
pthread_mutex_lock(&s->mtx);
s->frames_written += n / s->play.frame_size;
pthread_mutex_unlock(&s->mtx);
write_ofs += n;
rem -= n;
}
return 0;
}
/* 1 - Stopped by cubeb_stream_stop, otherwise 0 */ /* 1 - Stopped by cubeb_stream_stop, otherwise 0 */
static int static int
oss_audio_loop(cubeb_stream * s, cubeb_state *new_state) oss_audio_loop(cubeb_stream * s, cubeb_state *new_state)
@ -748,18 +788,10 @@ oss_audio_loop(cubeb_stream * s, cubeb_state *new_state)
cubeb_state state = CUBEB_STATE_STOPPED; cubeb_state state = CUBEB_STATE_STOPPED;
int trig = 0; int trig = 0;
int drain = 0; int drain = 0;
struct pollfd pfds[2]; const bool play_on = s->play.fd != -1, record_on = s->record.fd != -1;
unsigned int ppending, rpending; long nfr = s->bufframes;
pfds[0].fd = s->play.fd; if (record_on) {
pfds[0].events = POLLOUT;
pfds[1].fd = s->record.fd;
pfds[1].events = POLLIN;
ppending = 0;
rpending = s->bufframes;
if (s->record.fd != -1) {
if (ioctl(s->record.fd, SNDCTL_DSP_SETTRIGGER, &trig)) { if (ioctl(s->record.fd, SNDCTL_DSP_SETTRIGGER, &trig)) {
LOG("Error %d occured when setting trigger on record fd", errno); LOG("Error %d occured when setting trigger on record fd", errno);
state = CUBEB_STATE_ERROR; state = CUBEB_STATE_ERROR;
@ -771,43 +803,35 @@ oss_audio_loop(cubeb_stream * s, cubeb_state *new_state)
state = CUBEB_STATE_ERROR; state = CUBEB_STATE_ERROR;
goto breakdown; goto breakdown;
} }
memset(s->record.buf, 0, s->bufframes * s->record.frame_size); memset(s->record.buf, 0, s->bufframes * s->record.frame_size);
} }
while (1) { if (!play_on && !record_on) {
long nfr = 0; /*
* Stop here if the stream is not play & record stream,
* play-only stream or record-only stream
*/
goto breakdown;
}
while (1) {
pthread_mutex_lock(&s->mtx); pthread_mutex_lock(&s->mtx);
if (!s->running || s->destroying) { if (!s->running || s->destroying) {
pthread_mutex_unlock(&s->mtx); pthread_mutex_unlock(&s->mtx);
break; break;
} }
pthread_mutex_unlock(&s->mtx); pthread_mutex_unlock(&s->mtx);
if (s->play.fd == -1 && s->record.fd == -1) {
/*
* Stop here if the stream is not play & record stream,
* play-only stream or record-only stream
*/
goto breakdown; long got = 0;
} if (nfr > 0) {
got = s->data_cb(s, s->user_ptr, s->record.buf, s->play.buf, nfr);
while ((s->bufframes - ppending) >= s->nfr && rpending >= s->nfr) { if (got == CUBEB_ERROR) {
long n = ((s->bufframes - ppending) < rpending) ? s->bufframes - ppending : rpending;
char *rptr = NULL, *pptr = NULL;
if (s->record.fd != -1)
rptr = (char *)s->record.buf;
if (s->play.fd != -1)
pptr = (char *)s->play.buf + ppending * s->play.frame_size;
if (s->record.fd != -1 && s->record.floating) {
oss_linear32_to_float(s->record.buf, s->record.info.channels * n);
}
nfr = s->data_cb(s, s->user_ptr, rptr, pptr, n);
if (nfr == CUBEB_ERROR) {
state = CUBEB_STATE_ERROR; state = CUBEB_STATE_ERROR;
goto breakdown; goto breakdown;
} }
if (pptr) { if (play_on) {
float vol; float vol;
pthread_mutex_lock(&s->mtx); pthread_mutex_lock(&s->mtx);
@ -815,25 +839,15 @@ oss_audio_loop(cubeb_stream * s, cubeb_state *new_state)
pthread_mutex_unlock(&s->mtx); pthread_mutex_unlock(&s->mtx);
if (s->play.floating) { if (s->play.floating) {
oss_float_to_linear32(pptr, s->play.info.channels * nfr, vol); oss_float_to_linear32(s->play.buf, s->play.info.channels * got, vol);
} else { } else {
oss_linear16_set_vol((int16_t *)pptr, s->play.info.channels * nfr, vol); oss_linear16_set_vol((int16_t *)s->play.buf,
s->play.info.channels * got, vol);
} }
} }
if (pptr) { if (got < nfr) {
ppending += nfr;
assert(ppending <= s->bufframes);
}
if (rptr) {
assert(rpending >= nfr);
rpending -= nfr;
memmove(rptr, rptr + nfr * s->record.frame_size,
(s->bufframes - nfr) * s->record.frame_size);
}
if (nfr < n) {
if (s->play.fd != -1) { if (s->play.fd != -1) {
drain = 1; drain = 1;
break;
} else { } else {
/* /*
* This is a record-only stream and number of frames * This is a record-only stream and number of frames
@ -845,74 +859,48 @@ oss_audio_loop(cubeb_stream * s, cubeb_state *new_state)
goto breakdown; goto breakdown;
} }
} }
nfr = 0;
} }
ssize_t n, frames; if (got > 0) {
int nfds; if (play_on && oss_put_play_frames(s, got) < 0) {
pfds[0].revents = 0;
pfds[1].revents = 0;
nfds = poll(pfds, 2, 1000);
if (nfds == -1) {
if (errno == EINTR)
continue;
LOG("Error %d occured when polling playback and record fd", errno);
state = CUBEB_STATE_ERROR;
goto breakdown;
} else if (nfds == 0)
continue;
if ((pfds[0].revents & (POLLERR | POLLHUP)) ||
(pfds[1].revents & (POLLERR | POLLHUP))) {
LOG("Error occured on playback, record fds");
state = CUBEB_STATE_ERROR;
goto breakdown;
}
if (pfds[0].revents) {
while (ppending > 0) {
size_t bytes = ppending * s->play.frame_size;
if ((n = write(s->play.fd, (uint8_t *)s->play.buf, bytes)) < 0) {
if (errno == EINTR)
continue;
if (errno == EAGAIN) {
if (drain)
continue;
break;
}
state = CUBEB_STATE_ERROR; state = CUBEB_STATE_ERROR;
goto breakdown; goto breakdown;
}
frames = n / s->play.frame_size;
pthread_mutex_lock(&s->mtx);
s->frames_written += frames;
pthread_mutex_unlock(&s->mtx);
ppending -= frames;
memmove(s->play.buf, (uint8_t *)s->play.buf + n,
(s->bufframes - frames) * s->play.frame_size);
}
}
if (pfds[1].revents) {
while (s->bufframes - rpending > 0) {
size_t bytes = (s->bufframes - rpending) * s->record.frame_size;
size_t read_ofs = rpending * s->record.frame_size;
if ((n = read(s->record.fd, (uint8_t *)s->record.buf + read_ofs, bytes)) < 0) {
if (errno == EINTR)
continue;
if (errno == EAGAIN)
break;
state = CUBEB_STATE_ERROR;
goto breakdown;
}
frames = n / s->record.frame_size;
rpending += frames;
} }
} }
if (drain) { if (drain) {
state = CUBEB_STATE_DRAINED; state = CUBEB_STATE_DRAINED;
goto breakdown; goto breakdown;
} }
audio_buf_info bi;
if (play_on) {
if (ioctl(s->play.fd, SNDCTL_DSP_GETOSPACE, &bi)) {
state = CUBEB_STATE_ERROR;
goto breakdown;
}
/*
* In duplex mode, playback direction drives recording direction to
* prevent building up latencies.
*/
nfr = bi.fragsize * bi.fragments / s->play.frame_size;
if (nfr > s->bufframes) {
nfr = s->bufframes;
}
}
if (record_on) {
if (nfr == 0) {
nfr = s->nfr;
}
if (oss_get_rec_frames(s, nfr) == CUBEB_ERROR) {
state = CUBEB_STATE_ERROR;
goto breakdown;
}
if (s->record.floating) {
oss_linear32_to_float(s->record.buf, s->record.info.channels * nfr);
}
}
} }
return 1; return 1;
@ -1035,7 +1023,7 @@ oss_stream_init(cubeb * context,
goto error; goto error;
} }
if (s->record.fd == -1) { if (s->record.fd == -1) {
if ((s->record.fd = open(s->record.name, O_RDONLY | O_NONBLOCK)) == -1) { if ((s->record.fd = open(s->record.name, O_RDONLY)) == -1) {
LOG("Audio device \"%s\" could not be opened as read-only", LOG("Audio device \"%s\" could not be opened as read-only",
s->record.name); s->record.name);
ret = CUBEB_ERROR_DEVICE_UNAVAILABLE; ret = CUBEB_ERROR_DEVICE_UNAVAILABLE;
@ -1066,7 +1054,7 @@ oss_stream_init(cubeb * context,
goto error; goto error;
} }
if (s->play.fd == -1) { if (s->play.fd == -1) {
if ((s->play.fd = open(s->play.name, O_WRONLY | O_NONBLOCK)) == -1) { if ((s->play.fd = open(s->play.name, O_WRONLY)) == -1) {
LOG("Audio device \"%s\" could not be opened as write-only", LOG("Audio device \"%s\" could not be opened as write-only",
s->play.name); s->play.name);
ret = CUBEB_ERROR_DEVICE_UNAVAILABLE; ret = CUBEB_ERROR_DEVICE_UNAVAILABLE;
@ -1082,22 +1070,40 @@ oss_stream_init(cubeb * context,
s->play.frame_size = s->play.info.channels * (s->play.info.precision / 8); s->play.frame_size = s->play.info.channels * (s->play.info.precision / 8);
playnfr = (1 << oss_calc_frag_shift(s->nfr, s->play.frame_size)) / s->play.frame_size; playnfr = (1 << oss_calc_frag_shift(s->nfr, s->play.frame_size)) / s->play.frame_size;
} }
/* Use the largest nframes among playing and recording streams */ /*
* Use the largest nframes among playing and recording streams to set OSS buffer size.
* After that, use the smallest allocated nframes among both direction to allocate our
* temporary buffers.
*/
s->nfr = (playnfr > recnfr) ? playnfr : recnfr; s->nfr = (playnfr > recnfr) ? playnfr : recnfr;
s->nfrags = OSS_NFRAGS; s->nfrags = OSS_NFRAGS;
s->bufframes = s->nfr * s->nfrags;
if (s->play.fd != -1) { if (s->play.fd != -1) {
int frag = oss_get_frag_params(oss_calc_frag_shift(s->nfr, s->play.frame_size)); int frag = oss_get_frag_params(oss_calc_frag_shift(s->nfr, s->play.frame_size));
if (ioctl(s->record.fd, SNDCTL_DSP_SETFRAGMENT, &frag)) if (ioctl(s->play.fd, SNDCTL_DSP_SETFRAGMENT, &frag))
LOG("Failed to set record fd with SNDCTL_DSP_SETFRAGMENT. frag: 0x%x", LOG("Failed to set play fd with SNDCTL_DSP_SETFRAGMENT. frag: 0x%x",
frag); frag);
audio_buf_info bi;
if (ioctl(s->play.fd, SNDCTL_DSP_GETOSPACE, &bi))
LOG("Failed to get play fd's buffer info.");
else {
if (bi.fragsize / s->play.frame_size < s->nfr)
s->nfr = bi.fragsize / s->play.frame_size;
}
} }
if (s->record.fd != -1) { if (s->record.fd != -1) {
int frag = oss_get_frag_params(oss_calc_frag_shift(s->nfr, s->record.frame_size)); int frag = oss_get_frag_params(oss_calc_frag_shift(s->nfr, s->record.frame_size));
if (ioctl(s->record.fd, SNDCTL_DSP_SETFRAGMENT, &frag)) if (ioctl(s->record.fd, SNDCTL_DSP_SETFRAGMENT, &frag))
LOG("Failed to set record fd with SNDCTL_DSP_SETFRAGMENT. frag: 0x%x", LOG("Failed to set record fd with SNDCTL_DSP_SETFRAGMENT. frag: 0x%x",
frag); frag);
audio_buf_info bi;
if (ioctl(s->record.fd, SNDCTL_DSP_GETISPACE, &bi))
LOG("Failed to get record fd's buffer info.");
else {
if (bi.fragsize / s->record.frame_size < s->nfr)
s->nfr = bi.fragsize / s->record.frame_size;
}
} }
s->bufframes = s->nfr * s->nfrags;
s->context = context; s->context = context;
s->volume = 1.0; s->volume = 1.0;
s->state_cb = state_callback; s->state_cb = state_callback;

View file

@ -2030,6 +2030,23 @@ int setup_wasapi_stream_one_side(cubeb_stream * stm,
com_heap_ptr<WAVEFORMATEX> mix_format(tmp); com_heap_ptr<WAVEFORMATEX> mix_format(tmp);
mix_format->wBitsPerSample = stm->bytes_per_sample * 8; mix_format->wBitsPerSample = stm->bytes_per_sample * 8;
if (mix_format->wFormatTag == WAVE_FORMAT_PCM ||
mix_format->wFormatTag == WAVE_FORMAT_IEEE_FLOAT) {
switch (mix_format->wBitsPerSample) {
case 8:
case 16:
mix_format->wFormatTag = WAVE_FORMAT_PCM;
break;
case 32:
mix_format->wFormatTag = WAVE_FORMAT_IEEE_FLOAT;
break;
default:
LOG("%u bits per sample is incompatible with PCM wave formats",
mix_format->wBitsPerSample);
return CUBEB_ERROR;
}
}
if (mix_format->wFormatTag == WAVE_FORMAT_EXTENSIBLE) { if (mix_format->wFormatTag == WAVE_FORMAT_EXTENSIBLE) {
WAVEFORMATEXTENSIBLE * format_pcm = reinterpret_cast<WAVEFORMATEXTENSIBLE *>(mix_format.get()); WAVEFORMATEXTENSIBLE * format_pcm = reinterpret_cast<WAVEFORMATEXTENSIBLE *>(mix_format.get());
format_pcm->SubFormat = stm->waveformatextensible_sub_format; format_pcm->SubFormat = stm->waveformatextensible_sub_format;
@ -2085,9 +2102,11 @@ int setup_wasapi_stream_one_side(cubeb_stream * stm,
cubeb_device_info device_info; cubeb_device_info device_info;
int rv = wasapi_create_device(stm->context, device_info, stm->device_enumerator.get(), device.get()); int rv = wasapi_create_device(stm->context, device_info, stm->device_enumerator.get(), device.get());
if (rv == CUBEB_OK) { if (rv == CUBEB_OK) {
const char* HANDSFREE_TAG = "BTHHFEENUM"; const char* HANDSFREE_TAG = "BTHHFENUM";
size_t len = sizeof(HANDSFREE_TAG); size_t len = sizeof(HANDSFREE_TAG);
if (direction == eCapture && strncmp(device_info.group_id, HANDSFREE_TAG, len) == 0) { if (direction == eCapture &&
strlen(device_info.group_id) >= len &&
strncmp(device_info.group_id, HANDSFREE_TAG, len) == 0) {
// Rather high-latency to prevent constant under-runs in this particular // Rather high-latency to prevent constant under-runs in this particular
// case of an input device using bluetooth handsfree. // case of an input device using bluetooth handsfree.
uint32_t default_period_frames = hns_to_frames(device_info.default_rate, default_period); uint32_t default_period_frames = hns_to_frames(device_info.default_rate, default_period);
@ -2202,7 +2221,7 @@ void wasapi_find_matching_output_device(cubeb_stream * stm) {
for (uint32_t i = 0; i < collection.count; i++) { for (uint32_t i = 0; i < collection.count; i++) {
cubeb_device_info dev = collection.device[i]; cubeb_device_info dev = collection.device[i];
if (dev.type == CUBEB_DEVICE_TYPE_OUTPUT && if (dev.type == CUBEB_DEVICE_TYPE_OUTPUT &&
dev.group_id && input_device && !strcmp(dev.group_id, input_device->group_id) && dev.group_id && !strcmp(dev.group_id, input_device->group_id) &&
dev.default_rate == input_device->default_rate) { dev.default_rate == input_device->default_rate) {
LOG("Found matching device for %s: %s", input_device->friendly_name, dev.friendly_name); 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)); stm->output_device_id = utf8_to_wstr(reinterpret_cast<char const *>(dev.devid));