From 2f5e31cf47d8b340989395d84d966d1d1390fe3f Mon Sep 17 00:00:00 2001 From: wm4 Date: Tue, 10 Mar 2015 10:34:41 +0100 Subject: 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. --- audio/out/ao_coreaudio_exclusive.c | 115 ++++++++++++------------------------- 1 file changed, 37 insertions(+), 78 deletions(-) diff --git a/audio/out/ao_coreaudio_exclusive.c b/audio/out/ao_coreaudio_exclusive.c index cccd4c5808..43c17c415c 100644 --- a/audio/out/ao_coreaudio_exclusive.c +++ b/audio/out/ao_coreaudio_exclusive.c @@ -35,21 +35,18 @@ * when you are wanting to do good buffering of audio). */ +#include + #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, -- cgit v1.2.3