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
This commit is contained in:
Paul Adenot 2015-01-19 14:17:52 +01:00
Родитель a40d0afff0
Коммит 3e77a5e5ed
1 изменённых файлов: 98 добавлений и 93 удалений

Просмотреть файл

@ -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<float *>(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<uint64_t>(static_cast<double>(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);