From 0eb929dfa8bc3308342eb7629aa9bb2ee9b59a25 Mon Sep 17 00:00:00 2001 From: Paul Adenot Date: Tue, 17 Sep 2013 02:39:30 -0400 Subject: [PATCH] Bug 904617: Part 1 - Add a way to get cube latency, add wasapi latency functions r=kinetik --- layout/media/symbols.def.in | 1 + media/libcubeb/include/cubeb.h | 9 ++ media/libcubeb/src/cubeb-internal.h | 1 + media/libcubeb/src/cubeb.c | 10 +++ media/libcubeb/src/cubeb_alsa.c | 18 +++- media/libcubeb/src/cubeb_audiotrack.c | 18 +++- media/libcubeb/src/cubeb_audiounit.c | 113 +++++++++++++++++++++++++- media/libcubeb/src/cubeb_opensl.c | 11 ++- media/libcubeb/src/cubeb_pulse.c | 23 +++++- media/libcubeb/src/cubeb_sndio.c | 12 ++- media/libcubeb/src/cubeb_wasapi.cpp | 4 +- media/libcubeb/src/cubeb_winmm.c | 24 +++++- 12 files changed, 235 insertions(+), 9 deletions(-) diff --git a/layout/media/symbols.def.in b/layout/media/symbols.def.in index 0398ed72a4e..a6fb9ae72ca 100644 --- a/layout/media/symbols.def.in +++ b/layout/media/symbols.def.in @@ -120,6 +120,7 @@ cubeb_stream_get_position cubeb_stream_init cubeb_stream_start cubeb_stream_stop +cubeb_stream_get_latency #endif #ifdef MOZ_OGG th_comment_clear diff --git a/media/libcubeb/include/cubeb.h b/media/libcubeb/include/cubeb.h index 18933b1aea5..84cb0f59428 100644 --- a/media/libcubeb/include/cubeb.h +++ b/media/libcubeb/include/cubeb.h @@ -234,6 +234,15 @@ int cubeb_stream_stop(cubeb_stream * stream); @retval CUBEB_ERROR */ int cubeb_stream_get_position(cubeb_stream * stream, uint64_t * position); +/** Get the latency for this stream, in frames. This is the number of frames + between the time cubeb acquires the data in the callback and the listener + can hear the sound. + @param stream + @param latency Current approximate stream latency in ms + @retval CUBEB_OK + @retval CUBEB_ERROR */ +int cubeb_stream_get_latency(cubeb_stream * stream, uint32_t * latency); + #if defined(__cplusplus) } #endif diff --git a/media/libcubeb/src/cubeb-internal.h b/media/libcubeb/src/cubeb-internal.h index 33fa268f7ba..470c6794972 100644 --- a/media/libcubeb/src/cubeb-internal.h +++ b/media/libcubeb/src/cubeb-internal.h @@ -23,6 +23,7 @@ struct cubeb_ops { int (* stream_start)(cubeb_stream * stream); int (* stream_stop)(cubeb_stream * stream); int (* stream_get_position)(cubeb_stream * stream, uint64_t * position); + int (* stream_get_latency)(cubeb_stream * stream, uint32_t * latency); }; #endif /* CUBEB_INTERNAL_0eb56756_4e20_4404_a76d_42bf88cd15a5 */ diff --git a/media/libcubeb/src/cubeb.c b/media/libcubeb/src/cubeb.c index f136b731b42..75bb3635e0f 100644 --- a/media/libcubeb/src/cubeb.c +++ b/media/libcubeb/src/cubeb.c @@ -224,3 +224,13 @@ cubeb_stream_get_position(cubeb_stream * stream, uint64_t * position) return stream->context->ops->stream_get_position(stream, position); } + +int +cubeb_stream_get_latency(cubeb_stream * stream, uint32_t * latency) +{ + if (!stream || !latency) { + return CUBEB_ERROR_INVALID_PARAMETER; + } + + return stream->context->ops->stream_get_latency(stream, latency); +} diff --git a/media/libcubeb/src/cubeb_alsa.c b/media/libcubeb/src/cubeb_alsa.c index c5e5ce3bc00..f18100fb19f 100644 --- a/media/libcubeb/src/cubeb_alsa.c +++ b/media/libcubeb/src/cubeb_alsa.c @@ -1009,6 +1009,21 @@ alsa_stream_get_position(cubeb_stream * stm, uint64_t * position) return CUBEB_OK; } +int +alsa_stream_get_latency(cubeb_stream * stm, uint32_t * latency) +{ + snd_pcm_sframes_t delay; + /* This function returns the delay in frames until a frame written using + snd_pcm_writei is sent to the DAC. The DAC delay should be < 1ms anyways. */ + if (snd_pcm_delay(stm->pcm, &delay)) { + return CUBEB_ERROR; + } + + *latency = delay; + + return CUBEB_OK; +} + static struct cubeb_ops const alsa_ops = { .init = alsa_init, .get_backend_id = alsa_get_backend_id, @@ -1018,5 +1033,6 @@ static struct cubeb_ops const alsa_ops = { .stream_destroy = alsa_stream_destroy, .stream_start = alsa_stream_start, .stream_stop = alsa_stream_stop, - .stream_get_position = alsa_stream_get_position + .stream_get_position = alsa_stream_get_position, + .stream_get_latency = alsa_stream_get_latency }; diff --git a/media/libcubeb/src/cubeb_audiotrack.c b/media/libcubeb/src/cubeb_audiotrack.c index 52873819aad..0df7b036d19 100644 --- a/media/libcubeb/src/cubeb_audiotrack.c +++ b/media/libcubeb/src/cubeb_audiotrack.c @@ -5,7 +5,9 @@ * accompanying file LICENSE for details. */ +#if !defined(NDEBUG) #define NDEBUG +#endif #include #include #include @@ -424,6 +426,19 @@ audiotrack_stream_get_position(cubeb_stream * stream, uint64_t * position) return CUBEB_OK; } +int +audiotrack_stream_get_latency(cubeb_stream * stream, uint32_t * latency) +{ + assert(stream->instance && latency); + + /* Android returns the latency in ms, we want it in frames. */ + *latency = stream->context->klass.latency(stream->instance); + /* with rate <= 96000, we won't overflow until 44.739 seconds of latency */ + *latency = (*latency * stream->params.rate) / 1000; + + return 0; +} + static struct cubeb_ops const audiotrack_ops = { .init = audiotrack_init, .get_backend_id = audiotrack_get_backend_id, @@ -433,5 +448,6 @@ static struct cubeb_ops const audiotrack_ops = { .stream_destroy = audiotrack_stream_destroy, .stream_start = audiotrack_stream_start, .stream_stop = audiotrack_stream_stop, - .stream_get_position = audiotrack_stream_get_position + .stream_get_position = audiotrack_stream_get_position, + .stream_get_latency = audiotrack_stream_get_latency }; diff --git a/media/libcubeb/src/cubeb_audiounit.c b/media/libcubeb/src/cubeb_audiounit.c index 02deaefd0c3..8271e12cfb9 100644 --- a/media/libcubeb/src/cubeb_audiounit.c +++ b/media/libcubeb/src/cubeb_audiounit.c @@ -10,8 +10,10 @@ #include #include #include +#include #include "cubeb/cubeb.h" #include "cubeb-internal.h" +#include "prtime.h" #define NBUFS 4 @@ -33,8 +35,23 @@ struct cubeb_stream { uint64_t frames_queued; int shutdown; int draining; + uint64_t current_latency_frames; + uint64_t hw_latency_frames; }; +static int64_t +audiotimestamp_to_latency(AudioTimeStamp const * tstamp, cubeb_stream * stream) +{ + if (!(tstamp->mFlags & kAudioTimeStampHostTimeValid)) { + return 0; + } + + uint64_t pres = AudioConvertHostTimeToNanos(tstamp->mHostTime); + uint64_t now = AudioConvertHostTimeToNanos(AudioGetCurrentHostTime()); + + return ((pres - now) * stream->sample_spec.mSampleRate) / PR_NSEC_PER_SEC; +} + static OSStatus audiounit_output_callback(void * user_ptr, AudioUnitRenderActionFlags * flags, AudioTimeStamp const * tstamp, UInt32 bus, UInt32 nframes, @@ -52,6 +69,8 @@ audiounit_output_callback(void * user_ptr, AudioUnitRenderActionFlags * flags, pthread_mutex_lock(&stm->mutex); + stm->current_latency_frames = audiotimestamp_to_latency(tstamp, stm); + if (stm->draining || stm->shutdown) { pthread_mutex_unlock(&stm->mutex); if (stm->draining) { @@ -255,6 +274,8 @@ audiounit_stream_init(cubeb * context, cubeb_stream ** stream, char const * stre stm->frames_played = 0; stm->frames_queued = 0; + stm->current_latency_frames = 0; + stm->hw_latency_frames = UINT64_MAX; #if MAC_OS_X_VERSION_MIN_REQUIRED < 1060 r = OpenAComponent(comp, &stm->unit); @@ -351,6 +372,95 @@ audiounit_stream_get_position(cubeb_stream * stm, uint64_t * position) return CUBEB_OK; } +int +audiounit_stream_get_latency(cubeb_stream * stm, uint32_t * latency) +{ + pthread_mutex_lock(&stm->mutex); + if (stm->hw_latency_frames == UINT64_MAX) { + UInt32 size; + uint32_t device_latency_frames, device_safety_offset; + double unit_latency_sec; + AudioDeviceID output_device_id; + OSStatus r; + + AudioObjectPropertyAddress output_device_address = { + kAudioHardwarePropertyDefaultOutputDevice, + kAudioObjectPropertyScopeGlobal, + kAudioObjectPropertyElementMaster + }; + AudioObjectPropertyAddress latency_address = { + kAudioDevicePropertyLatency, + kAudioDevicePropertyScopeOutput, + kAudioObjectPropertyElementMaster + }; + AudioObjectPropertyAddress safety_offset_address = { + kAudioDevicePropertySafetyOffset, + kAudioDevicePropertyScopeOutput, + kAudioObjectPropertyElementMaster + }; + + + size = sizeof(output_device_id); + r = AudioObjectGetPropertyData(kAudioObjectSystemObject, + &output_device_address, + 0, + 0, + &size, + &output_device_id); + if (r != noErr) { + pthread_mutex_unlock(&stm->mutex); + return CUBEB_ERROR; + } + + size = sizeof(unit_latency_sec); + r = AudioUnitGetProperty(stm->unit, + kAudioUnitProperty_Latency, + kAudioUnitScope_Global, + 0, + &unit_latency_sec, + &size); + if (r != noErr) { + pthread_mutex_unlock(&stm->mutex); + return CUBEB_ERROR; + } + + size = sizeof(device_latency_frames); + r = AudioObjectGetPropertyData(output_device_id, + &latency_address, + 0, + NULL, + &size, + &device_latency_frames); + if (r != noErr) { + pthread_mutex_unlock(&stm->mutex); + return CUBEB_ERROR; + } + + size = sizeof(device_safety_offset); + r = AudioObjectGetPropertyData(output_device_id, + &safety_offset_address, + 0, + NULL, + &size, + &device_safety_offset); + if (r != noErr) { + pthread_mutex_unlock(&stm->mutex); + return CUBEB_ERROR; + } + + // This part is fixed and depend on the stream parameter and the hardware. + stm->hw_latency_frames = + (uint32_t)(unit_latency_sec * stm->sample_spec.mSampleRate) + + device_latency_frames + + device_safety_offset; + } + + *latency = stm->hw_latency_frames + stm->current_latency_frames; + pthread_mutex_unlock(&stm->mutex); + + return CUBEB_OK; +} + static struct cubeb_ops const audiounit_ops = { .init = audiounit_init, .get_backend_id = audiounit_get_backend_id, @@ -360,5 +470,6 @@ static struct cubeb_ops const audiounit_ops = { .stream_destroy = audiounit_stream_destroy, .stream_start = audiounit_stream_start, .stream_stop = audiounit_stream_stop, - .stream_get_position = audiounit_stream_get_position + .stream_get_position = audiounit_stream_get_position, + .stream_get_latency = audiounit_stream_get_latency }; diff --git a/media/libcubeb/src/cubeb_opensl.c b/media/libcubeb/src/cubeb_opensl.c index f1183f4473e..481457f64b7 100644 --- a/media/libcubeb/src/cubeb_opensl.c +++ b/media/libcubeb/src/cubeb_opensl.c @@ -413,6 +413,14 @@ opensl_stream_get_position(cubeb_stream * stm, uint64_t * position) return CUBEB_OK; } +int +opensl_stream_get_latency(cubeb_stream * stm, uint32_t * latency) +{ + *latency = NBUFS * stm->queuebuf_len; + + return CUBEB_OK; +} + static struct cubeb_ops const opensl_ops = { .init = opensl_init, .get_backend_id = opensl_get_backend_id, @@ -422,5 +430,6 @@ static struct cubeb_ops const opensl_ops = { .stream_destroy = opensl_stream_destroy, .stream_start = opensl_stream_start, .stream_stop = opensl_stream_stop, - .stream_get_position = opensl_stream_get_position + .stream_get_position = opensl_stream_get_position, + .stream_get_latency = opensl_stream_get_latency }; diff --git a/media/libcubeb/src/cubeb_pulse.c b/media/libcubeb/src/cubeb_pulse.c index be481d06d7d..94c068e1f7f 100644 --- a/media/libcubeb/src/cubeb_pulse.c +++ b/media/libcubeb/src/cubeb_pulse.c @@ -568,6 +568,26 @@ pulse_stream_get_position(cubeb_stream * stm, uint64_t * position) return CUBEB_OK; } +int +pulse_stream_get_latency(cubeb_stream * stm, uint32_t * latency) +{ + pa_usec_t r_usec; + int negative, r; + + if (!stm) { + return CUBEB_ERROR; + } + + r = WRAP(pa_stream_get_latency)(stm->stream, &r_usec, &negative); + assert(!negative); + if (r) { + return CUBEB_ERROR; + } + + *latency = (r_usec * stm->sample_spec.rate) / PR_NSEC_PER_SEC; + return CUBEB_OK; +} + static struct cubeb_ops const pulse_ops = { .init = pulse_init, .get_backend_id = pulse_get_backend_id, @@ -577,5 +597,6 @@ static struct cubeb_ops const pulse_ops = { .stream_destroy = pulse_stream_destroy, .stream_start = pulse_stream_start, .stream_stop = pulse_stream_stop, - .stream_get_position = pulse_stream_get_position + .stream_get_position = pulse_stream_get_position, + .stream_get_latency = pulse_stream_get_latency }; diff --git a/media/libcubeb/src/cubeb_sndio.c b/media/libcubeb/src/cubeb_sndio.c index d2866d50f3b..cb473a32ce6 100644 --- a/media/libcubeb/src/cubeb_sndio.c +++ b/media/libcubeb/src/cubeb_sndio.c @@ -314,6 +314,15 @@ sndio_stream_set_volume(cubeb_stream *s, float volume) return CUBEB_OK; } +int +sndio_stream_get_latency(cubeb_stream * stm, uint32_t * latency) +{ + // http://www.openbsd.org/cgi-bin/man.cgi?query=sio_open + // in the "Measuring the latency and buffers usage" paragraph. + *latency = stm->wrpos - stm->rdpos; + return CUBEB_OK; +} + static struct cubeb_ops const sndio_ops = { .init = sndio_init, .get_backend_id = sndio_get_backend_id, @@ -322,5 +331,6 @@ static struct cubeb_ops const sndio_ops = { .stream_destroy = sndio_stream_destroy, .stream_start = sndio_stream_start, .stream_stop = sndio_stream_stop, - .stream_get_position = sndio_stream_get_position + .stream_get_position = sndio_stream_get_position, + .stream_get_latency = sndio_stream_get_latency }; diff --git a/media/libcubeb/src/cubeb_wasapi.cpp b/media/libcubeb/src/cubeb_wasapi.cpp index 2ba57943968..7110ffc8971 100644 --- a/media/libcubeb/src/cubeb_wasapi.cpp +++ b/media/libcubeb/src/cubeb_wasapi.cpp @@ -878,8 +878,8 @@ cubeb_ops const wasapi_ops = { /*.stream_destroy =*/ wasapi_stream_destroy, /*.stream_start =*/ wasapi_stream_start, /*.stream_stop =*/ wasapi_stream_stop, - /*.stream_get_position =*/ wasapi_stream_get_position - ///*.stream_get_latency =*/ wasapi_stream_get_latency + /*.stream_get_position =*/ wasapi_stream_get_position, + /*.stream_get_latency =*/ wasapi_stream_get_latency }; } // namespace anonymous diff --git a/media/libcubeb/src/cubeb_winmm.c b/media/libcubeb/src/cubeb_winmm.c index 82a0459bb57..9d117a49c2d 100644 --- a/media/libcubeb/src/cubeb_winmm.c +++ b/media/libcubeb/src/cubeb_winmm.c @@ -64,6 +64,7 @@ struct cubeb_stream { HANDLE event; HWAVEOUT waveout; CRITICAL_SECTION lock; + uint64_t written; }; static size_t @@ -144,6 +145,7 @@ winmm_refill_stream(cubeb_stream * stm) } else if (got < wanted) { stm->draining = 1; } + stm->written += got; assert(hdr->dwFlags & WHDR_PREPARED); @@ -389,6 +391,7 @@ winmm_stream_init(cubeb * context, cubeb_stream ** stream, char const * stream_n stm->data_callback = data_callback; stm->state_callback = state_callback; stm->user_ptr = user_ptr; + stm->written = 0; if (latency < context->minimum_latency) { latency = context->minimum_latency; @@ -574,6 +577,24 @@ winmm_stream_get_position(cubeb_stream * stm, uint64_t * position) return CUBEB_OK; } +int +winmm_stream_get_latency(cubeb_stream * stm, uint32_t * latency) +{ + MMRESULT r; + MMTIME time; + uint64_t written; + + EnterCriticalSection(&stm->lock); + time.wType = TIME_SAMPLES; + r = waveOutGetPosition(stm->waveout, &time, sizeof(time)); + written = stm->written; + LeaveCriticalSection(&stm->lock); + + *latency = written - time.u.sample; + + return CUBEB_OK; +} + static struct cubeb_ops const winmm_ops = { /*.init =*/ winmm_init, /*.get_backend_id =*/ winmm_get_backend_id, @@ -583,5 +604,6 @@ static struct cubeb_ops const winmm_ops = { /*.stream_destroy =*/ winmm_stream_destroy, /*.stream_start =*/ winmm_stream_start, /*.stream_stop =*/ winmm_stream_stop, - /*.stream_get_position =*/ winmm_stream_get_position + /*.stream_get_position =*/ winmm_stream_get_position, + /*.stream_get_latency = */ winmm_stream_get_latency };