From 3e77a5e5edd0f4f3e32f8b6627b9b6db1fddc9c8 Mon Sep 17 00:00:00 2001 From: Paul Adenot Date: Mon, 19 Jan 2015 14:17:52 +0100 Subject: [PATCH] Bug 698079 - Synthetize the clock when using WASAPI to prevent A/V desynchronization issues when switching the default audio output device. r=kinetik --HG-- extra : rebase_source : c50798149111b680b4565798d33d43e6e062fb2c --- media/libcubeb/src/cubeb_wasapi.cpp | 191 ++++++++++++++-------------- 1 file changed, 98 insertions(+), 93 deletions(-) diff --git a/media/libcubeb/src/cubeb_wasapi.cpp b/media/libcubeb/src/cubeb_wasapi.cpp index 678146cad3d4..10dd59fbf7b5 100644 --- a/media/libcubeb/src/cubeb_wasapi.cpp +++ b/media/libcubeb/src/cubeb_wasapi.cpp @@ -26,12 +26,12 @@ #define STACK_SIZE_PARAM_IS_A_RESERVATION 0x00010000 // Threads only #endif -#define LOGGING_ENABLED +// #define LOGGING_ENABLED #ifdef LOGGING_ENABLED # define LOG(...) do { \ - fprintf(stderr, __VA_ARGS__); \ - fprintf(stderr, "\n"); \ + fprintf(stdout, __VA_ARGS__); \ + fprintf(stdout, "\n"); \ } while(0); #else # define LOG(...) @@ -75,6 +75,20 @@ void SafeRelease(T * ptr) } } +struct auto_lock { + auto_lock(CRITICAL_SECTION * lock) + :lock(lock) + { + EnterCriticalSection(lock); + } + ~auto_lock() + { + LeaveCriticalSection(lock); + } +private: + CRITICAL_SECTION * lock; +}; + struct auto_com { auto_com() : need_uninit(true) { @@ -105,7 +119,7 @@ private: }; typedef HANDLE (WINAPI *set_mm_thread_characteristics_function)( - const char* TaskName, LPDWORD TaskIndex); + const char * TaskName, LPDWORD TaskIndex); typedef BOOL (WINAPI *revert_mm_thread_characteristics_function)(HANDLE handle); extern cubeb_ops const wasapi_ops; @@ -113,12 +127,10 @@ 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); +int setup_wasapi_stream(cubeb_stream * stm); } -struct cubeb_stream; - struct cubeb { @@ -130,6 +142,62 @@ struct cubeb revert_mm_thread_characteristics_function revert_mm_thread_characteristics; }; +class wasapi_endpoint_notification_client; + +struct cubeb_stream +{ + cubeb * context; + /* Mixer pameters. We need to convert the input + * stream to this samplerate/channel layout, as WASAPI + * 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. */ + IAudioRenderClient * render_client; + /* 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; + /* This is set by WASAPI when we should refill the stream. */ + HANDLE refill_event; + /* Each cubeb_stream has its own thread. */ + HANDLE thread; + /* We synthetize our clock from the callbacks. */ + LONG64 clock; + CRITICAL_SECTION stream_reset_lock; + /* Maximum number of frames we can be requested in a callback. */ + uint32_t buffer_frame_count; + /* Resampler instance. Resampling will only happen if necessary. */ + cubeb_resampler * resampler; + /* Buffer used to downmix or upmix to the number of channels the mixer has. + * its size is |frames_to_bytes_before_mix(buffer_frame_count)|. */ + float * mix_buffer; + /* True if the stream is draining. */ + bool draining; +}; + class wasapi_endpoint_notification_client : public IMMNotificationClient { @@ -188,10 +256,13 @@ public: /* 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); + { + auto_lock lock(&stm->stream_reset_lock); + 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; @@ -232,60 +303,6 @@ private: cubeb_stream * stm; }; -struct cubeb_stream -{ - cubeb * context; - /* Mixer pameters. We need to convert the input - * stream to this samplerate/channel layout, as WASAPI - * 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. */ - IAudioRenderClient * render_client; - /* Interface pointer to use the clock facilities. */ - 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; - /* This is set by WASAPI when we should refill the stream. */ - HANDLE refill_event; - /* Each cubeb_stream has its own thread. */ - HANDLE thread; - uint64_t clock_freq; - /* Maximum number of frames we can be requested in a callback. */ - uint32_t buffer_frame_count; - /* Resampler instance. Resampling will only happen if necessary. */ - cubeb_resampler * resampler; - /* Buffer used to downmix or upmix to the number of channels the mixer has. - * its size is |frames_to_bytes_before_mix(buffer_frame_count)|. */ - float * mix_buffer; - /* True if the stream is draining. */ - bool draining; -}; - namespace { bool should_upmix(cubeb_stream * stream) { @@ -370,6 +387,8 @@ refill(cubeb_stream * stm, float * data, long frames_needed) dest = data; } + stm->clock = InterlockedAdd64(&stm->clock, frames_needed); + long out_frames = cubeb_resampler_fill(stm->resampler, dest, frames_needed); /* XXX: Handle this error. */ @@ -458,7 +477,7 @@ wasapi_stream_render_loop(LPVOID stream) continue; } - BYTE* data; + BYTE * data; hr = stm->render_client->GetBuffer(available, &data); if (SUCCEEDED(hr)) { refill(stm, reinterpret_cast(data), available); @@ -629,7 +648,7 @@ void wasapi_destroy(cubeb * context) free(context); } -char const* wasapi_get_backend_id(cubeb * context) +char const * wasapi_get_backend_id(cubeb * context) { return "wasapi"; } @@ -908,14 +927,6 @@ int setup_wasapi_stream(cubeb_stream * stm) return CUBEB_ERROR; } - hr = stm->client->GetService(__uuidof(IAudioClock), - (void **)&stm->audio_clock); - if (FAILED(hr)) { - LOG("Could not get the IAudioClock, %x", hr); - wasapi_stream_destroy(stm); - return CUBEB_ERROR; - } - hr = stm->client->GetService(__uuidof(IAudioStreamVolume), (void **)&stm->audio_stream_volume); if (FAILED(hr)) { @@ -924,13 +935,6 @@ int setup_wasapi_stream(cubeb_stream * stm) return CUBEB_ERROR; } - hr = stm->audio_clock->GetFrequency(&stm->clock_freq); - if (FAILED(hr)) { - LOG("failed to get audio clock frequency, %x", hr); - wasapi_stream_destroy(stm); - return CUBEB_ERROR; - } - /* If we are playing a mono stream, we only resample one channel, * and copy it over, so we are always resampling the number * of channels of the stream, not the number of channels @@ -973,6 +977,8 @@ wasapi_stream_init(cubeb * context, cubeb_stream ** stream, stm->stream_params = stream_params; stm->draining = false; stm->latency = latency; + stm->clock = 0; + InitializeCriticalSection(&stm->stream_reset_lock); stm->shutdown_event = CreateEvent(NULL, 0, 0, NULL); stm->refill_event = CreateEvent(NULL, 0, 0, NULL); @@ -1013,7 +1019,6 @@ void close_wasapi_stream(cubeb_stream * stm) SafeRelease(stm->client); SafeRelease(stm->render_client); - SafeRelease(stm->audio_clock); cubeb_resampler_destroy(stm->resampler); @@ -1033,6 +1038,7 @@ void wasapi_stream_destroy(cubeb_stream * stm) SafeRelease(stm->shutdown_event); SafeRelease(stm->refill_event); + DeleteCriticalSection(&stm->stream_reset_lock); close_wasapi_stream(stm); @@ -1045,6 +1051,8 @@ int wasapi_stream_start(cubeb_stream * stm) { HRESULT hr; + auto_lock lock(&stm->stream_reset_lock); + assert(stm); stm->thread = (HANDLE) _beginthreadex(NULL, 256 * 1024, wasapi_stream_render_loop, stm, STACK_SIZE_PARAM_IS_A_RESERVATION, NULL); @@ -1068,6 +1076,8 @@ int wasapi_stream_stop(cubeb_stream * stm) { assert(stm && stm->shutdown_event); + auto_lock lock(&stm->stream_reset_lock); + SetEvent(stm->shutdown_event); HRESULT hr = stm->client->Stop(); @@ -1090,16 +1100,7 @@ int wasapi_stream_get_position(cubeb_stream * stm, uint64_t * position) { assert(stm && position); - UINT64 pos; - HRESULT hr; - - hr = stm->audio_clock->GetPosition(&pos, NULL); - if (FAILED(hr)) { - LOG("Could not get accurate position: %x\n", hr); - return CUBEB_ERROR; - } - - *position = static_cast(static_cast(pos) / stm->clock_freq * stm->stream_params.rate); + *position = InterlockedAdd64(&stm->clock, 0); return CUBEB_OK; } @@ -1108,6 +1109,8 @@ int wasapi_stream_get_latency(cubeb_stream * stm, uint32_t * latency) { assert(stm && latency); + auto_lock lock(&stm->stream_reset_lock); + /* The GetStreamLatency method only works if the * AudioClient has been initialized. */ if (!stm->client) { @@ -1129,6 +1132,8 @@ int wasapi_stream_set_volume(cubeb_stream * stm, float volume) /* up to 9.1 for now */ float volumes[10]; + auto_lock lock(&stm->stream_reset_lock); + hr = stm->audio_stream_volume->GetChannelCount(&channels); if (hr != S_OK) { LOG("could not get the channel count: %x", hr);