mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Bug 698079 - When using the WASAPI backend, always output audio to the default audio device. r=kinetik
This is implemented by detecting when the default audio output device changes, stopping the current stream, and starting a new one using the new default audio output device. --HG-- extra : rebase_source : 94df344aecca0f940ba7216275176763336beb96
This commit is contained in:
parent
9b761caf78
commit
f2a853a96e
@ -26,7 +26,9 @@
|
||||
#define STACK_SIZE_PARAM_IS_A_RESERVATION 0x00010000 // Threads only
|
||||
#endif
|
||||
|
||||
#if 1
|
||||
#define LOGGING_ENABLED
|
||||
|
||||
#ifdef LOGGING_ENABLED
|
||||
# define LOG(...) do { \
|
||||
fprintf(stderr, __VA_ARGS__); \
|
||||
fprintf(stderr, "\n"); \
|
||||
@ -107,8 +109,17 @@ typedef HANDLE (WINAPI *set_mm_thread_characteristics_function)(
|
||||
typedef BOOL (WINAPI *revert_mm_thread_characteristics_function)(HANDLE handle);
|
||||
|
||||
extern cubeb_ops const wasapi_ops;
|
||||
|
||||
int wasapi_stream_stop(cubeb_stream * stm);
|
||||
int wasapi_stream_start(cubeb_stream * stm);
|
||||
void close_wasapi_stream(cubeb_stream * stm);
|
||||
HRESULT setup_wasapi_stream(cubeb_stream * stm);
|
||||
|
||||
}
|
||||
|
||||
struct cubeb_stream;
|
||||
|
||||
|
||||
struct cubeb
|
||||
{
|
||||
cubeb_ops const * ops;
|
||||
@ -119,6 +130,108 @@ struct cubeb
|
||||
revert_mm_thread_characteristics_function revert_mm_thread_characteristics;
|
||||
};
|
||||
|
||||
|
||||
class wasapi_endpoint_notification_client : public IMMNotificationClient
|
||||
{
|
||||
public:
|
||||
/* The implementation of MSCOM was copied from MSDN. */
|
||||
ULONG STDMETHODCALLTYPE
|
||||
AddRef()
|
||||
{
|
||||
return InterlockedIncrement(&ref_count);
|
||||
}
|
||||
|
||||
ULONG STDMETHODCALLTYPE
|
||||
Release()
|
||||
{
|
||||
ULONG ulRef = InterlockedDecrement(&ref_count);
|
||||
if (0 == ulRef) {
|
||||
delete this;
|
||||
}
|
||||
return ulRef;
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE
|
||||
QueryInterface(REFIID riid, VOID **ppvInterface)
|
||||
{
|
||||
if (IID_IUnknown == riid) {
|
||||
AddRef();
|
||||
*ppvInterface = (IUnknown*)this;
|
||||
} else if (__uuidof(IMMNotificationClient) == riid) {
|
||||
AddRef();
|
||||
*ppvInterface = (IMMNotificationClient*)this;
|
||||
} else {
|
||||
*ppvInterface = NULL;
|
||||
return E_NOINTERFACE;
|
||||
}
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
wasapi_endpoint_notification_client(cubeb_stream * stm)
|
||||
: ref_count(1)
|
||||
, stm(stm)
|
||||
{ }
|
||||
|
||||
HRESULT STDMETHODCALLTYPE
|
||||
OnDefaultDeviceChanged(EDataFlow flow, ERole role, LPCWSTR device_id)
|
||||
{
|
||||
/* we don't support capture for now. */
|
||||
if (flow == eCapture) {
|
||||
return S_OK;
|
||||
}
|
||||
/* all our streams are eMultimedia for now */
|
||||
if (role != eMultimedia) {
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
auto_com autocom;
|
||||
|
||||
/* Close the stream */
|
||||
wasapi_stream_stop(stm);
|
||||
close_wasapi_stream(stm);
|
||||
/* Reopen a stream and start it immediately. This will automatically pick the
|
||||
* new default device for this role. */
|
||||
setup_wasapi_stream(stm);
|
||||
wasapi_stream_start(stm);
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
/* The remaining methods are not implemented, they simply log when called (if
|
||||
* log is enabled), for debugging. */
|
||||
HRESULT STDMETHODCALLTYPE OnDeviceAdded(LPCWSTR device_id)
|
||||
{
|
||||
LOG("Audio device added.");
|
||||
return S_OK;
|
||||
};
|
||||
|
||||
HRESULT STDMETHODCALLTYPE OnDeviceRemoved(LPCWSTR device_id)
|
||||
{
|
||||
LOG("Audio device removed.");
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE
|
||||
OnDeviceStateChanged(LPCWSTR device_id, DWORD new_state)
|
||||
{
|
||||
LOG("Audio device state changed.");
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE
|
||||
OnPropertyValueChanged(LPCWSTR device_id, const PROPERTYKEY key)
|
||||
{
|
||||
LOG("Audio device property value changed.");
|
||||
return S_OK;
|
||||
}
|
||||
private:
|
||||
/* refcount for this instance, necessary to implement MSCOM semantics. */
|
||||
LONG ref_count;
|
||||
/* Pointer to the stream. It is guaranteed that this pointer is
|
||||
* always valid. */
|
||||
cubeb_stream * stm;
|
||||
};
|
||||
|
||||
struct cubeb_stream
|
||||
{
|
||||
cubeb * context;
|
||||
@ -127,9 +240,18 @@ struct cubeb_stream
|
||||
* does not resample nor upmix itself. */
|
||||
cubeb_stream_params mix_params;
|
||||
cubeb_stream_params stream_params;
|
||||
/* The latency initially requested for this stream. */
|
||||
unsigned latency;
|
||||
cubeb_state_callback state_callback;
|
||||
cubeb_data_callback data_callback;
|
||||
void * user_ptr;
|
||||
|
||||
/* Lifetime considerations:
|
||||
* - client, render_client, audio_clock and audio_stream_volume are interface
|
||||
* pointer to the IAudioClient.
|
||||
* - The lifetime for device_enumerator and notification_client, resampler,
|
||||
* mix_buffer are the same as the cubeb_stream instance. */
|
||||
|
||||
/* Main handle on the WASAPI stream. */
|
||||
IAudioClient * client;
|
||||
/* Interface pointer to use the event-driven interface. */
|
||||
@ -138,6 +260,13 @@ struct cubeb_stream
|
||||
IAudioClock * audio_clock;
|
||||
/* Interface pointer to use the volume facilities. */
|
||||
IAudioStreamVolume * audio_stream_volume;
|
||||
/* Device enumerator to be able to be notified when the default
|
||||
* device change. */
|
||||
IMMDeviceEnumerator * device_enumerator;
|
||||
/* Device notification client, to be able to be notified when the default
|
||||
* audio device changes and route the audio to the new default audio output
|
||||
* device */
|
||||
wasapi_endpoint_notification_client * notification_client;
|
||||
/* This event is set by the stream_stop and stream_destroy
|
||||
* function, so the render loop can exit properly. */
|
||||
HANDLE shutdown_event;
|
||||
@ -372,12 +501,55 @@ BOOL WINAPI revert_mm_thread_characteristics_noop(HANDLE mmcss_handle)
|
||||
return true;
|
||||
}
|
||||
|
||||
HRESULT register_notification_client(cubeb_stream * stm)
|
||||
{
|
||||
HRESULT hr = CoCreateInstance(__uuidof(MMDeviceEnumerator),
|
||||
NULL, CLSCTX_INPROC_SERVER,
|
||||
IID_PPV_ARGS(&stm->device_enumerator));
|
||||
|
||||
if (FAILED(hr)) {
|
||||
LOG("Could not get device enumerator: %x", hr);
|
||||
return hr;
|
||||
}
|
||||
|
||||
stm->notification_client = new wasapi_endpoint_notification_client(stm);
|
||||
|
||||
hr = stm->device_enumerator->RegisterEndpointNotificationCallback(stm->notification_client);
|
||||
|
||||
if (FAILED(hr)) {
|
||||
LOG("Could not register endpoint notification callback: %x", hr);
|
||||
return hr;
|
||||
}
|
||||
|
||||
return hr;
|
||||
}
|
||||
|
||||
HRESULT unregister_notification_client(cubeb_stream * stm)
|
||||
{
|
||||
HRESULT hr = CoCreateInstance(__uuidof(MMDeviceEnumerator),
|
||||
NULL, CLSCTX_INPROC_SERVER,
|
||||
IID_PPV_ARGS(&stm->device_enumerator));
|
||||
|
||||
if (FAILED(hr)) {
|
||||
LOG("Could not get device enumerator: %x", hr);
|
||||
return hr;
|
||||
}
|
||||
|
||||
stm->device_enumerator->UnregisterEndpointNotificationCallback(stm->notification_client);
|
||||
|
||||
SafeRelease(stm->notification_client);
|
||||
SafeRelease(stm->device_enumerator);
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
HRESULT get_default_endpoint(IMMDevice ** device)
|
||||
{
|
||||
IMMDeviceEnumerator * enumerator;
|
||||
HRESULT hr = CoCreateInstance(__uuidof(MMDeviceEnumerator),
|
||||
NULL, CLSCTX_INPROC_SERVER,
|
||||
IID_PPV_ARGS(&enumerator));
|
||||
|
||||
if (FAILED(hr)) {
|
||||
LOG("Could not get device enumerator.");
|
||||
return hr;
|
||||
@ -388,7 +560,7 @@ HRESULT get_default_endpoint(IMMDevice ** device)
|
||||
* and eCommunication ("Voice communication"). */
|
||||
hr = enumerator->GetDefaultAudioEndpoint(eRender, eMultimedia, device);
|
||||
if (FAILED(hr)) {
|
||||
LOG("Could not get default audio endpoint.");
|
||||
LOG("Could not get default audio endpoint. %d", __LINE__);
|
||||
SafeRelease(enumerator);
|
||||
return hr;
|
||||
}
|
||||
@ -650,46 +822,14 @@ handle_channel_layout(cubeb_stream * stm, WAVEFORMATEX ** mix_format, const cub
|
||||
}
|
||||
}
|
||||
|
||||
int
|
||||
wasapi_stream_init(cubeb * context, cubeb_stream ** stream,
|
||||
char const * stream_name, cubeb_stream_params stream_params,
|
||||
unsigned int latency, cubeb_data_callback data_callback,
|
||||
cubeb_state_callback state_callback, void * user_ptr)
|
||||
int setup_wasapi_stream(cubeb_stream * stm)
|
||||
{
|
||||
HRESULT hr;
|
||||
IMMDevice * device;
|
||||
WAVEFORMATEX * mix_format;
|
||||
|
||||
auto_com com;
|
||||
|
||||
assert(context && stream);
|
||||
|
||||
cubeb_stream * stm = (cubeb_stream *)calloc(1, sizeof(cubeb_stream));
|
||||
|
||||
assert(stm);
|
||||
|
||||
stm->context = context;
|
||||
stm->data_callback = data_callback;
|
||||
stm->state_callback = state_callback;
|
||||
stm->user_ptr = user_ptr;
|
||||
stm->stream_params = stream_params;
|
||||
stm->draining = false;
|
||||
|
||||
stm->shutdown_event = CreateEvent(NULL, 0, 0, NULL);
|
||||
stm->refill_event = CreateEvent(NULL, 0, 0, NULL);
|
||||
|
||||
if (!stm->shutdown_event) {
|
||||
LOG("Can't create the shutdown event, error: %x.", GetLastError());
|
||||
wasapi_stream_destroy(stm);
|
||||
return CUBEB_ERROR;
|
||||
}
|
||||
|
||||
if (!stm->refill_event) {
|
||||
SafeRelease(stm->shutdown_event);
|
||||
LOG("Can't create the refill event, error: %x.", GetLastError());
|
||||
wasapi_stream_destroy(stm);
|
||||
return CUBEB_ERROR;
|
||||
}
|
||||
|
||||
IMMDevice * device;
|
||||
hr = get_default_endpoint(&device);
|
||||
if (FAILED(hr)) {
|
||||
LOG("Could not get default endpoint, error: %x", hr);
|
||||
@ -718,7 +858,7 @@ wasapi_stream_init(cubeb * context, cubeb_stream ** stream,
|
||||
return CUBEB_ERROR;
|
||||
}
|
||||
|
||||
handle_channel_layout(stm, &mix_format, &stream_params);
|
||||
handle_channel_layout(stm, &mix_format, &stm->stream_params);
|
||||
|
||||
/* Shared mode WASAPI always supports float32 sample format, so this
|
||||
* is safe. */
|
||||
@ -729,7 +869,7 @@ wasapi_stream_init(cubeb * context, cubeb_stream ** stream,
|
||||
hr = stm->client->Initialize(AUDCLNT_SHAREMODE_SHARED,
|
||||
AUDCLNT_STREAMFLAGS_EVENTCALLBACK |
|
||||
AUDCLNT_STREAMFLAGS_NOPERSIST,
|
||||
ms_to_hns(latency),
|
||||
ms_to_hns(stm->latency),
|
||||
0,
|
||||
mix_format,
|
||||
NULL);
|
||||
@ -795,11 +935,11 @@ wasapi_stream_init(cubeb * context, cubeb_stream ** stream,
|
||||
* and copy it over, so we are always resampling the number
|
||||
* of channels of the stream, not the number of channels
|
||||
* that WASAPI wants. */
|
||||
stm->resampler = cubeb_resampler_create(stm, stream_params,
|
||||
stm->resampler = cubeb_resampler_create(stm, stm->stream_params,
|
||||
stm->mix_params.rate,
|
||||
data_callback,
|
||||
stm->data_callback,
|
||||
stm->buffer_frame_count,
|
||||
user_ptr,
|
||||
stm->user_ptr,
|
||||
CUBEB_RESAMPLER_QUALITY_DESKTOP);
|
||||
if (!stm->resampler) {
|
||||
LOG("Could not get a resampler");
|
||||
@ -807,11 +947,79 @@ wasapi_stream_init(cubeb * context, cubeb_stream ** stream,
|
||||
return CUBEB_ERROR;
|
||||
}
|
||||
|
||||
return CUBEB_OK;
|
||||
}
|
||||
|
||||
int
|
||||
wasapi_stream_init(cubeb * context, cubeb_stream ** stream,
|
||||
char const * stream_name, cubeb_stream_params stream_params,
|
||||
unsigned int latency, cubeb_data_callback data_callback,
|
||||
cubeb_state_callback state_callback, void * user_ptr)
|
||||
{
|
||||
HRESULT hr;
|
||||
int rv;
|
||||
auto_com com;
|
||||
|
||||
assert(context && stream);
|
||||
|
||||
cubeb_stream * stm = (cubeb_stream *)calloc(1, sizeof(cubeb_stream));
|
||||
|
||||
assert(stm);
|
||||
|
||||
stm->context = context;
|
||||
stm->data_callback = data_callback;
|
||||
stm->state_callback = state_callback;
|
||||
stm->user_ptr = user_ptr;
|
||||
stm->stream_params = stream_params;
|
||||
stm->draining = false;
|
||||
stm->latency = latency;
|
||||
|
||||
stm->shutdown_event = CreateEvent(NULL, 0, 0, NULL);
|
||||
stm->refill_event = CreateEvent(NULL, 0, 0, NULL);
|
||||
|
||||
if (!stm->shutdown_event) {
|
||||
LOG("Can't create the shutdown event, error: %x.", GetLastError());
|
||||
wasapi_stream_destroy(stm);
|
||||
return CUBEB_ERROR;
|
||||
}
|
||||
|
||||
if (!stm->refill_event) {
|
||||
SafeRelease(stm->shutdown_event);
|
||||
LOG("Can't create the refill event, error: %x.", GetLastError());
|
||||
wasapi_stream_destroy(stm);
|
||||
return CUBEB_ERROR;
|
||||
}
|
||||
|
||||
rv = setup_wasapi_stream(stm);
|
||||
if (rv != CUBEB_OK) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
hr = register_notification_client(stm);
|
||||
if (FAILED(hr)) {
|
||||
/* this is not fatal, we can still play audio, but we won't be able
|
||||
* to keep using the default audio endpoint if it changes. */
|
||||
LOG("failed to register notification client, %x", hr);
|
||||
}
|
||||
|
||||
*stream = stm;
|
||||
|
||||
return CUBEB_OK;
|
||||
}
|
||||
|
||||
void close_wasapi_stream(cubeb_stream * stm)
|
||||
{
|
||||
assert(stm);
|
||||
|
||||
SafeRelease(stm->client);
|
||||
SafeRelease(stm->render_client);
|
||||
SafeRelease(stm->audio_clock);
|
||||
|
||||
cubeb_resampler_destroy(stm->resampler);
|
||||
|
||||
free(stm->mix_buffer);
|
||||
}
|
||||
|
||||
void wasapi_stream_destroy(cubeb_stream * stm)
|
||||
{
|
||||
assert(stm);
|
||||
@ -826,14 +1034,10 @@ void wasapi_stream_destroy(cubeb_stream * stm)
|
||||
SafeRelease(stm->shutdown_event);
|
||||
SafeRelease(stm->refill_event);
|
||||
|
||||
SafeRelease(stm->client);
|
||||
SafeRelease(stm->render_client);
|
||||
SafeRelease(stm->audio_clock);
|
||||
SafeRelease(stm->audio_stream_volume);
|
||||
close_wasapi_stream(stm);
|
||||
|
||||
cubeb_resampler_destroy(stm->resampler);
|
||||
unregister_notification_client(stm);
|
||||
|
||||
free(stm->mix_buffer);
|
||||
free(stm);
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user