ao_coreaudio_exclusive: port to pull API, fix latency calculations

Instead of maintaining a private ring buffer, use the generic support
for audio APIs with pull callbacks (internally called AO pull API). This
also fixes latency calculations: instead of just returning the
ringbuffer status, the audio playback state is calculated better and
includes interpolation.

The main reason this wasn't done earlier was mid-stream format
switching. The pull API can now handle it (in a way) by destroying and
recreating the AO. This is a bit brutal, but quite simple. It's untested
in this new AO, though. Some details might not be right, like how ot
restores the old format when reloading.
This commit is contained in:
wm4 2015-03-10 10:34:41 +01:00
parent fa75a7b6d7
commit 2f5e31cf47
1 changed files with 37 additions and 78 deletions

View File

@ -35,21 +35,18 @@
* when you are wanting to do good buffering of audio).
*/
#include <CoreAudio/HostTime.h>
#include "config.h"
#include "ao.h"
#include "internal.h"
#include "audio/format.h"
#include "osdep/timer.h"
#include "options/m_option.h"
#include "misc/ring.h"
#include "common/msg.h"
#include "audio/out/ao_coreaudio_properties.h"
#include "audio/out/ao_coreaudio_utils.h"
static void audio_pause(struct ao *ao);
static void audio_resume(struct ao *ao);
static void reset(struct ao *ao);
static bool ca_format_is_digital(AudioStreamBasicDescription asbd)
{
switch (asbd.mFormatID)
@ -310,8 +307,6 @@ struct priv {
bool paused;
struct mp_ring *buffer;
// digital render callback
AudioDeviceIOProcID render_cb;
@ -332,13 +327,10 @@ struct priv {
bool changed_mixing;
int stream_asbd_changed;
};
bool reload_requested;
static int get_ring_size(struct ao *ao)
{
return af_fmt_seconds_to_bytes(
ao->format, 0.5, ao->channels.num, ao->samplerate);
}
uint32_t hw_latency_us;
};
static OSStatus render_cb_digital(
AudioDeviceID device, const AudioTimeStamp *ts,
@ -350,7 +342,29 @@ static OSStatus render_cb_digital(
AudioBuffer buf = out_data->mBuffers[p->stream_idx];
int requested = buf.mDataByteSize;
mp_ring_read(p->buffer, buf.mData, requested);
int pseudo_frames = requested / ao->sstride;
// we expect the callback to read full frames, which are aligned accordingly
if (pseudo_frames * ao->sstride != requested) {
MP_ERR(ao, "Unsupported unaligned read of %d bytes.\n", requested);
return kAudioHardwareUnspecifiedError;
}
int64_t end = mp_time_us();
end += p->hw_latency_us + ca_get_latency(ts)
+ ca_frames_to_us(ao, pseudo_frames);
ao_read_data(ao, &buf.mData, pseudo_frames, end);
// Check whether we need to reset the digital output stream.
if (p->stream_asbd_changed) {
p->stream_asbd_changed = 0;
if (!p->reload_requested && ca_stream_supports_digital(ao, p->stream)) {
p->reload_requested = true;
ao_request_reload(ao);
MP_INFO(ao, "Stream format changed! Reloading.\n");
}
}
return noErr;
}
@ -487,17 +501,22 @@ static int init_digital(struct ao *ao, AudioStreamBasicDescription asbd)
(p->stream_asbd.mBytesPerPacket /
p->stream_asbd.mFramesPerPacket);
p->buffer = mp_ring_new(p, get_ring_size(ao));
uint32_t latency_frames = 0;
err = CA_GET_O(p->device, kAudioDevicePropertyLatency, &latency_frames);
if (err != noErr) {
CHECK_CA_WARN("cannot get device latency");
latency_frames = 0;
}
p->hw_latency_us = ca_frames_to_us(ao, latency_frames);
MP_VERBOSE(ao, "base latency: %d microseconds\n", (int)p->hw_latency_us);
err = AudioDeviceCreateIOProcID(p->device,
(AudioDeviceIOProc)render_cb_digital,
(void *)ao,
&p->render_cb);
CHECK_CA_ERROR("failed to register digital render callback");
reset(ao);
return CONTROL_TRUE;
coreaudio_error:
@ -506,52 +525,6 @@ coreaudio_error:
return CONTROL_ERROR;
}
static int play(struct ao *ao, void **data, int samples, int flags)
{
struct priv *p = ao->priv;
void *output_samples = data[0];
int num_bytes = samples * ao->sstride;
// Check whether we need to reset the digital output stream.
if (p->stream_asbd_changed) {
p->stream_asbd_changed = 0;
if (ca_stream_supports_digital(ao, p->stream)) {
if (!ca_change_format(ao, p->stream, p->stream_asbd)) {
MP_WARN(ao , "can't restore digital output\n");
} else {
MP_WARN(ao, "restoring digital output succeeded.\n");
reset(ao);
}
}
}
int wrote = mp_ring_write(p->buffer, output_samples, num_bytes);
audio_resume(ao);
return wrote / ao->sstride;
}
static void reset(struct ao *ao)
{
struct priv *p = ao->priv;
audio_pause(ao);
mp_ring_reset(p->buffer);
}
static int get_space(struct ao *ao)
{
struct priv *p = ao->priv;
return mp_ring_available(p->buffer) / ao->sstride;
}
static double get_delay(struct ao *ao)
{
// FIXME: should also report the delay of coreaudio itself (hardware +
// internal buffers)
struct priv *p = ao->priv;
return mp_ring_buffered(p->buffer) / (double)ao->bps;
}
static void uninit(struct ao *ao)
{
struct priv *p = ao->priv;
@ -581,26 +554,16 @@ static void audio_pause(struct ao *ao)
{
struct priv *p = ao->priv;
if (p->paused)
return;
OSStatus err = AudioDeviceStop(p->device, p->render_cb);
CHECK_CA_WARN("can't stop digital device");
p->paused = true;
}
static void audio_resume(struct ao *ao)
{
struct priv *p = ao->priv;
if (!p->paused)
return;
OSStatus err = AudioDeviceStart(p->device, p->render_cb);
CHECK_CA_WARN("can't start digital device");
p->paused = false;
}
#define OPT_BASE_STRUCT struct priv
@ -610,10 +573,6 @@ const struct ao_driver audio_out_coreaudio_exclusive = {
.name = "coreaudio_exclusive",
.uninit = uninit,
.init = init,
.play = play,
.get_space = get_space,
.get_delay = get_delay,
.reset = reset,
.pause = audio_pause,
.resume = audio_resume,
.list_devs = ca_get_device_list,