зеркало из https://github.com/mozilla/gecko-dev.git
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:
Родитель
a40d0afff0
Коммит
3e77a5e5ed
|
@ -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);
|
||||
|
|
Загрузка…
Ссылка в новой задаче