Bug 904617: Part 1 - Add a way to get cube latency, add wasapi latency functions r=kinetik

This commit is contained in:
Paul Adenot 2013-09-17 02:39:30 -04:00
parent 7c615652fb
commit 0eb929dfa8
12 changed files with 235 additions and 9 deletions

View File

@ -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

View File

@ -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

View File

@ -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 */

View File

@ -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);
}

View File

@ -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
};

View File

@ -5,7 +5,9 @@
* accompanying file LICENSE for details.
*/
#if !defined(NDEBUG)
#define NDEBUG
#endif
#include <assert.h>
#include <pthread.h>
#include <stdlib.h>
@ -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
};

View File

@ -10,8 +10,10 @@
#include <stdlib.h>
#include <AudioUnit/AudioUnit.h>
#include <CoreAudio/AudioHardware.h>
#include <CoreAudio/HostTime.h>
#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
};

View File

@ -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
};

View File

@ -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
};

View File

@ -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
};

View File

@ -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

View File

@ -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
};