@ -4,7 +4,7 @@
* This program is made available under an ISC - style license . See the
* accompanying file LICENSE for details .
*/
# define _WIN32_WINNT 0x060 0
# define _WIN32_WINNT 0x060 3
# 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 ;
stm - > role = eConsole ;
stm - > input_bluetooth_handsfree = false ;
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 ;
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 ;
}
* latency = hns_to_frames ( stm , latency_hns ) ;
// 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 ) ;
}
}
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 ) )
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 ) )
}
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 ) )
}
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 ,