Bug 1136360 - Account for stream and device latency in stream position calculation. r=jesup

This commit is contained in:
Matthew Gregan 2015-08-21 13:53:21 +12:00
parent ec86ab61bc
commit 5faa47f98e

View File

@ -220,6 +220,17 @@ struct cubeb_stream
IAudioRenderClient * render_client;
/* Interface pointer to use the volume facilities. */
IAudioStreamVolume * audio_stream_volume;
/* Interface pointer to use the stream audio clock. */
IAudioClock * audio_clock;
/* Frames written to the stream since it was opened. Reset on device
* change. Uses mix_params.rate. */
UINT64 frames_written;
/* Frames written to the (logical) stream since it was first
* created. Updated on device change. Uses stream_params.rate. */
UINT64 total_frames_written;
/* Last valid reported stream position. Used to ensure the position
* reported by stream_get_position increases monotonically. */
UINT64 prev_position;
/* Device enumerator to be able to be notified when the default
* device change. */
IMMDeviceEnumerator * device_enumerator;
@ -237,8 +248,9 @@ struct cubeb_stream
HANDLE refill_event;
/* Each cubeb_stream has its own thread. */
HANDLE thread;
/* We synthesize our clock from the callbacks. */
LONG64 clock;
/* The lock protects all members that are touched by the render thread or
change during a device reset, including: audio_clock, audio_stream_volume,
client, frames_written, mix_params, total_frames_written, prev_position. */
owned_critical_section * stream_reset_lock;
/* Maximum number of frames we can be requested in a callback. */
uint32_t buffer_frame_count;
@ -343,16 +355,6 @@ private:
};
namespace {
void clock_add(cubeb_stream * stm, LONG64 value)
{
InterlockedExchangeAdd64(&stm->clock, value);
}
LONG64 clock_get(cubeb_stream * stm)
{
return InterlockedExchangeAdd64(&stm->clock, 0);
}
bool should_upmix(cubeb_stream * stream)
{
return stream->mix_params.channels > stream->stream_params.channels;
@ -363,10 +365,10 @@ bool should_downmix(cubeb_stream * stream)
return stream->mix_params.channels < stream->stream_params.channels;
}
float stream_to_mix_samplerate_ratio(cubeb_stream * stream)
double stream_to_mix_samplerate_ratio(cubeb_stream * stream)
{
auto_lock lock(stream->stream_reset_lock);
return float(stream->stream_params.rate) / stream->mix_params.rate;
stream->stream_reset_lock->assert_current_thread_owns();
return double(stream->stream_params.rate) / stream->mix_params.rate;
}
/* Upmix function, copies a mono channel into L and R */
@ -452,7 +454,10 @@ refill(cubeb_stream * stm, float * data, long frames_needed)
long out_frames = cubeb_resampler_fill(stm->resampler, dest, frames_needed);
clock_add(stm, roundf(frames_needed * stream_to_mix_samplerate_ratio(stm)));
{
auto_lock lock(stm->stream_reset_lock);
stm->frames_written += out_frames;
}
/* XXX: Handle this error. */
if (out_frames < 0) {
@ -688,6 +693,33 @@ HRESULT get_default_endpoint(IMMDevice ** device)
return ERROR_SUCCESS;
}
double
current_stream_delay(cubeb_stream * stm)
{
stm->stream_reset_lock->assert_current_thread_owns();
UINT64 freq;
HRESULT hr = stm->audio_clock->GetFrequency(&freq);
if (FAILED(hr)) {
LOG("GetFrequency failed: %x\n", hr);
return 0;
}
UINT64 pos;
hr = stm->audio_clock->GetPosition(&pos, NULL);
if (FAILED(hr)) {
LOG("GetPosition failed: %x\n", hr);
return 0;
}
double cur_pos = static_cast<double>(pos) / freq;
double max_pos = static_cast<double>(stm->frames_written) / stm->mix_params.rate;
double delay = max_pos - cur_pos;
XASSERT(delay >= 0);
return delay;
}
} // namespace anonymous
extern "C" {
@ -1069,6 +1101,14 @@ int setup_wasapi_stream(cubeb_stream * stm)
return CUBEB_ERROR;
}
XASSERT(stm->frames_written == 0);
hr = stm->client->GetService(__uuidof(IAudioClock),
(void **)&stm->audio_clock);
if (FAILED(hr)) {
LOG("Could not get the IAudioClock %x.\n", hr);
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
@ -1113,15 +1153,6 @@ wasapi_stream_init(cubeb * context, cubeb_stream ** stream,
stm->stream_params = stream_params;
stm->draining = false;
stm->latency = latency;
stm->clock = 0;
/* Null out WASAPI-specific state */
stm->resampler = NULL;
stm->client = NULL;
stm->render_client = NULL;
stm->audio_stream_volume = NULL;
stm->device_enumerator = NULL;
stm->notification_client = NULL;
stm->stream_reset_lock = new owned_critical_section();
@ -1178,6 +1209,11 @@ void close_wasapi_stream(cubeb_stream * stm)
SafeRelease(stm->audio_stream_volume);
stm->audio_stream_volume = NULL;
SafeRelease(stm->audio_clock);
stm->audio_clock = NULL;
stm->total_frames_written += round(stm->frames_written * stream_to_mix_samplerate_ratio(stm));
stm->frames_written = 0;
if (stm->resampler) {
cubeb_resampler_destroy(stm->resampler);
stm->resampler = NULL;
@ -1283,8 +1319,24 @@ int wasapi_stream_stop(cubeb_stream * stm)
int wasapi_stream_get_position(cubeb_stream * stm, uint64_t * position)
{
XASSERT(stm && position);
auto_lock lock(stm->stream_reset_lock);
*position = clock_get(stm);
/* Calculate how far behind the current stream head the playback cursor is. */
uint64_t stream_delay = current_stream_delay(stm) * stm->stream_params.rate;
/* Calculate the logical stream head in frames at the stream sample rate. */
uint64_t max_pos = stm->total_frames_written +
round(stm->frames_written * stream_to_mix_samplerate_ratio(stm));
*position = max_pos;
if (stream_delay <= *position) {
*position -= stream_delay;
}
if (*position < stm->prev_position) {
*position = stm->prev_position;
}
stm->prev_position = *position;
return CUBEB_OK;
}