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). * when you are wanting to do good buffering of audio).
*/ */
#include <CoreAudio/HostTime.h>
#include "config.h" #include "config.h"
#include "ao.h" #include "ao.h"
#include "internal.h" #include "internal.h"
#include "audio/format.h" #include "audio/format.h"
#include "osdep/timer.h" #include "osdep/timer.h"
#include "options/m_option.h" #include "options/m_option.h"
#include "misc/ring.h"
#include "common/msg.h" #include "common/msg.h"
#include "audio/out/ao_coreaudio_properties.h" #include "audio/out/ao_coreaudio_properties.h"
#include "audio/out/ao_coreaudio_utils.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) static bool ca_format_is_digital(AudioStreamBasicDescription asbd)
{ {
switch (asbd.mFormatID) switch (asbd.mFormatID)
@ -310,8 +307,6 @@ struct priv {
bool paused; bool paused;
struct mp_ring *buffer;
// digital render callback // digital render callback
AudioDeviceIOProcID render_cb; AudioDeviceIOProcID render_cb;
@ -332,13 +327,10 @@ struct priv {
bool changed_mixing; bool changed_mixing;
int stream_asbd_changed; int stream_asbd_changed;
}; bool reload_requested;
static int get_ring_size(struct ao *ao) uint32_t hw_latency_us;
{ };
return af_fmt_seconds_to_bytes(
ao->format, 0.5, ao->channels.num, ao->samplerate);
}
static OSStatus render_cb_digital( static OSStatus render_cb_digital(
AudioDeviceID device, const AudioTimeStamp *ts, AudioDeviceID device, const AudioTimeStamp *ts,
@ -350,7 +342,29 @@ static OSStatus render_cb_digital(
AudioBuffer buf = out_data->mBuffers[p->stream_idx]; AudioBuffer buf = out_data->mBuffers[p->stream_idx];
int requested = buf.mDataByteSize; 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; return noErr;
} }
@ -487,17 +501,22 @@ static int init_digital(struct ao *ao, AudioStreamBasicDescription asbd)
(p->stream_asbd.mBytesPerPacket / (p->stream_asbd.mBytesPerPacket /
p->stream_asbd.mFramesPerPacket); 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, err = AudioDeviceCreateIOProcID(p->device,
(AudioDeviceIOProc)render_cb_digital, (AudioDeviceIOProc)render_cb_digital,
(void *)ao, (void *)ao,
&p->render_cb); &p->render_cb);
CHECK_CA_ERROR("failed to register digital render callback"); CHECK_CA_ERROR("failed to register digital render callback");
reset(ao);
return CONTROL_TRUE; return CONTROL_TRUE;
coreaudio_error: coreaudio_error:
@ -506,52 +525,6 @@ coreaudio_error:
return CONTROL_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) static void uninit(struct ao *ao)
{ {
struct priv *p = ao->priv; struct priv *p = ao->priv;
@ -581,26 +554,16 @@ static void audio_pause(struct ao *ao)
{ {
struct priv *p = ao->priv; struct priv *p = ao->priv;
if (p->paused)
return;
OSStatus err = AudioDeviceStop(p->device, p->render_cb); OSStatus err = AudioDeviceStop(p->device, p->render_cb);
CHECK_CA_WARN("can't stop digital device"); CHECK_CA_WARN("can't stop digital device");
p->paused = true;
} }
static void audio_resume(struct ao *ao) static void audio_resume(struct ao *ao)
{ {
struct priv *p = ao->priv; struct priv *p = ao->priv;
if (!p->paused)
return;
OSStatus err = AudioDeviceStart(p->device, p->render_cb); OSStatus err = AudioDeviceStart(p->device, p->render_cb);
CHECK_CA_WARN("can't start digital device"); CHECK_CA_WARN("can't start digital device");
p->paused = false;
} }
#define OPT_BASE_STRUCT struct priv #define OPT_BASE_STRUCT struct priv
@ -610,10 +573,6 @@ const struct ao_driver audio_out_coreaudio_exclusive = {
.name = "coreaudio_exclusive", .name = "coreaudio_exclusive",
.uninit = uninit, .uninit = uninit,
.init = init, .init = init,
.play = play,
.get_space = get_space,
.get_delay = get_delay,
.reset = reset,
.pause = audio_pause, .pause = audio_pause,
.resume = audio_resume, .resume = audio_resume,
.list_devs = ca_get_device_list, .list_devs = ca_get_device_list,