mirror of https://github.com/mpv-player/mpv
719 lines
22 KiB
C
719 lines
22 KiB
C
/*
|
|
* CoreAudio audio output driver for Mac OS X
|
|
*
|
|
* original copyright (C) Timothy J. Wood - Aug 2000
|
|
* ported to MPlayer libao2 by Dan Christiansen
|
|
*
|
|
* The S/PDIF part of the code is based on the auhal audio output
|
|
* module from VideoLAN:
|
|
* Copyright (c) 2006 Derk-Jan Hartman <hartman at videolan dot org>
|
|
*
|
|
* This file is part of MPlayer.
|
|
*
|
|
* MPlayer is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* MPlayer is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License along
|
|
* along with MPlayer; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
*/
|
|
|
|
/*
|
|
* The MacOS X CoreAudio framework doesn't mesh as simply as some
|
|
* simpler frameworks do. This is due to the fact that CoreAudio pulls
|
|
* audio samples rather than having them pushed at it (which is nice
|
|
* when you are wanting to do good buffering of audio).
|
|
*/
|
|
|
|
#include "config.h"
|
|
#include "ao.h"
|
|
#include "audio/format.h"
|
|
#include "osdep/timer.h"
|
|
#include "mpvcore/m_option.h"
|
|
#include "mpvcore/mp_ring.h"
|
|
#include "mpvcore/mp_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 void print_buffer(struct ao *ao, struct mp_ring *buffer)
|
|
{
|
|
void *tctx = talloc_new(NULL);
|
|
MP_VERBOSE(ao, "%s\n", mp_ring_repr(buffer, tctx));
|
|
talloc_free(tctx);
|
|
}
|
|
|
|
struct priv_d {
|
|
// digital render callback
|
|
AudioDeviceIOProcID render_cb;
|
|
|
|
// pid set for hog mode, (-1) means that hog mode on the device was
|
|
// released. hog mode is exclusive access to a device
|
|
pid_t hog_pid;
|
|
|
|
// stream selected for digital playback by the detection in init
|
|
AudioStreamID stream;
|
|
|
|
// stream index in an AudioBufferList
|
|
int stream_idx;
|
|
|
|
// format we changed the stream to: for the digital case each application
|
|
// sets the stream format for a device to what it needs
|
|
AudioStreamBasicDescription stream_asbd;
|
|
AudioStreamBasicDescription original_asbd;
|
|
|
|
bool changed_mixing;
|
|
int stream_asbd_changed;
|
|
bool muted;
|
|
};
|
|
|
|
struct priv {
|
|
AudioDeviceID device; // selected device
|
|
bool is_digital; // running in digital mode?
|
|
|
|
AudioUnit audio_unit; // AudioUnit for lpcm output
|
|
|
|
bool paused;
|
|
|
|
struct mp_ring *buffer;
|
|
struct priv_d *digital;
|
|
|
|
// options
|
|
int opt_device_id;
|
|
int opt_list;
|
|
};
|
|
|
|
static int get_ring_size(struct ao *ao)
|
|
{
|
|
return af_fmt_seconds_to_bytes(
|
|
ao->format, 0.5, ao->channels.num, ao->samplerate);
|
|
}
|
|
|
|
static OSStatus render_cb_lpcm(void *ctx, AudioUnitRenderActionFlags *aflags,
|
|
const AudioTimeStamp *ts, UInt32 bus,
|
|
UInt32 frames, AudioBufferList *buffer_list)
|
|
{
|
|
struct ao *ao = ctx;
|
|
struct priv *p = ao->priv;
|
|
|
|
AudioBuffer buf = buffer_list->mBuffers[0];
|
|
int requested = buf.mDataByteSize;
|
|
|
|
if (mp_ring_buffered(p->buffer) < requested) {
|
|
MP_VERBOSE(ao, "buffer underrun\n");
|
|
audio_pause(ao);
|
|
} else {
|
|
mp_ring_read(p->buffer, buf.mData, requested);
|
|
}
|
|
|
|
return noErr;
|
|
}
|
|
|
|
static OSStatus render_cb_digital(
|
|
AudioDeviceID device, const AudioTimeStamp *ts,
|
|
const void *in_data, const AudioTimeStamp *in_ts,
|
|
AudioBufferList *out_data, const AudioTimeStamp *out_ts, void *ctx)
|
|
{
|
|
struct ao *ao = ctx;
|
|
struct priv *p = ao->priv;
|
|
struct priv_d *d = p->digital;
|
|
AudioBuffer buf = out_data->mBuffers[d->stream_idx];
|
|
int requested = buf.mDataByteSize;
|
|
|
|
if (d->muted)
|
|
mp_ring_drain(p->buffer, requested);
|
|
else
|
|
mp_ring_read(p->buffer, buf.mData, requested);
|
|
|
|
return noErr;
|
|
}
|
|
|
|
static int control(struct ao *ao, enum aocontrol cmd, void *arg)
|
|
{
|
|
struct priv *p = ao->priv;
|
|
ao_control_vol_t *control_vol;
|
|
OSStatus err;
|
|
Float32 vol;
|
|
switch (cmd) {
|
|
case AOCONTROL_GET_VOLUME:
|
|
control_vol = (ao_control_vol_t *)arg;
|
|
if (p->is_digital) {
|
|
struct priv_d *d = p->digital;
|
|
// Digital output has no volume adjust.
|
|
int vol = d->muted ? 0 : 100;
|
|
*control_vol = (ao_control_vol_t) {
|
|
.left = vol, .right = vol,
|
|
};
|
|
return CONTROL_TRUE;
|
|
}
|
|
|
|
err = AudioUnitGetParameter(p->audio_unit, kHALOutputParam_Volume,
|
|
kAudioUnitScope_Global, 0, &vol);
|
|
|
|
CHECK_CA_ERROR("could not get HAL output volume");
|
|
control_vol->left = control_vol->right = vol * 100.0;
|
|
return CONTROL_TRUE;
|
|
|
|
case AOCONTROL_SET_VOLUME:
|
|
control_vol = (ao_control_vol_t *)arg;
|
|
|
|
if (p->is_digital) {
|
|
struct priv_d *d = p->digital;
|
|
// Digital output can not set volume. Here we have to return true
|
|
// to make mixer forget it. Else mixer will add a soft filter,
|
|
// that's not we expected and the filter not support ac3 stream
|
|
// will cause mplayer die.
|
|
|
|
// Although not support set volume, but at least we support mute.
|
|
// MPlayer set mute by set volume to zero, we handle it.
|
|
if (control_vol->left == 0 && control_vol->right == 0)
|
|
d->muted = true;
|
|
else
|
|
d->muted = false;
|
|
return CONTROL_TRUE;
|
|
}
|
|
|
|
vol = (control_vol->left + control_vol->right) / 200.0;
|
|
err = AudioUnitSetParameter(p->audio_unit, kHALOutputParam_Volume,
|
|
kAudioUnitScope_Global, 0, vol, 0);
|
|
|
|
CHECK_CA_ERROR("could not set HAL output volume");
|
|
return CONTROL_TRUE;
|
|
|
|
} // end switch
|
|
return CONTROL_UNKNOWN;
|
|
|
|
coreaudio_error:
|
|
return CONTROL_ERROR;
|
|
}
|
|
|
|
static void print_list(struct ao *ao)
|
|
{
|
|
char *help = talloc_strdup(NULL, "Available output devices:\n");
|
|
|
|
AudioDeviceID *devs;
|
|
size_t n_devs;
|
|
|
|
OSStatus err =
|
|
CA_GET_ARY(kAudioObjectSystemObject, kAudioHardwarePropertyDevices,
|
|
&devs, &n_devs);
|
|
|
|
CHECK_CA_ERROR("Failed to get list of output devices.");
|
|
|
|
for (int i = 0; i < n_devs; i++) {
|
|
char *name;
|
|
OSStatus err = CA_GET_STR(devs[i], kAudioObjectPropertyName, &name);
|
|
|
|
if (err == noErr)
|
|
talloc_steal(devs, name);
|
|
else
|
|
name = "Unknown";
|
|
|
|
help = talloc_asprintf_append(
|
|
help, " * %s (id: %" PRIu32 ")\n", name, devs[i]);
|
|
}
|
|
|
|
talloc_free(devs);
|
|
|
|
coreaudio_error:
|
|
MP_INFO(ao, "%s", help);
|
|
talloc_free(help);
|
|
}
|
|
|
|
static int init_lpcm(struct ao *ao, AudioStreamBasicDescription asbd);
|
|
static int init_digital(struct ao *ao, AudioStreamBasicDescription asbd);
|
|
|
|
static int init(struct ao *ao)
|
|
{
|
|
OSStatus err;
|
|
struct priv *p = ao->priv;
|
|
|
|
if (p->opt_list) print_list(ao);
|
|
|
|
struct priv_d *d = talloc_zero(p, struct priv_d);
|
|
|
|
*d = (struct priv_d) {
|
|
.muted = false,
|
|
.stream_asbd_changed = 0,
|
|
.hog_pid = -1,
|
|
.stream = 0,
|
|
.stream_idx = -1,
|
|
.changed_mixing = false,
|
|
};
|
|
|
|
p->digital = d;
|
|
|
|
ao->per_application_mixer = true;
|
|
ao->no_persistent_volume = true;
|
|
|
|
AudioDeviceID selected_device = 0;
|
|
if (p->opt_device_id < 0) {
|
|
// device not set by user, get the default one
|
|
err = CA_GET(kAudioObjectSystemObject,
|
|
kAudioHardwarePropertyDefaultOutputDevice,
|
|
&selected_device);
|
|
CHECK_CA_ERROR("could not get default audio device");
|
|
} else {
|
|
selected_device = p->opt_device_id;
|
|
}
|
|
|
|
char *device_name;
|
|
err = CA_GET_STR(selected_device, kAudioObjectPropertyName, &device_name);
|
|
CHECK_CA_ERROR("could not get selected audio device name");
|
|
|
|
MP_VERBOSE(ao, "selected audio output device: %s (%" PRIu32 ")\n",
|
|
device_name, selected_device);
|
|
|
|
talloc_free(device_name);
|
|
|
|
// Save selected device id
|
|
p->device = selected_device;
|
|
|
|
bool supports_digital = false;
|
|
/* Probe whether device support S/PDIF stream output if input is AC3 stream. */
|
|
if (AF_FORMAT_IS_AC3(ao->format)) {
|
|
if (ca_device_supports_digital(ao, selected_device))
|
|
supports_digital = true;
|
|
}
|
|
|
|
if (!supports_digital) {
|
|
AudioChannelLayout *layouts;
|
|
size_t n_layouts;
|
|
err = CA_GET_ARY_O(selected_device,
|
|
kAudioDevicePropertyPreferredChannelLayout,
|
|
&layouts, &n_layouts);
|
|
CHECK_CA_ERROR("could not get audio device prefered layouts");
|
|
|
|
uint32_t *bitmaps;
|
|
size_t n_bitmaps;
|
|
|
|
ca_bitmaps_from_layouts(ao, layouts, n_layouts, &bitmaps, &n_bitmaps);
|
|
talloc_free(layouts);
|
|
|
|
struct mp_chmap_sel chmap_sel = {0};
|
|
|
|
for (int i=0; i < n_bitmaps; i++) {
|
|
struct mp_chmap chmap = {0};
|
|
mp_chmap_from_lavc(&chmap, bitmaps[i]);
|
|
mp_chmap_sel_add_map(&chmap_sel, &chmap);
|
|
}
|
|
|
|
talloc_free(bitmaps);
|
|
|
|
if (ao->channels.num < 3 || n_bitmaps < 1)
|
|
// If the input is not surround or we could not get any usable
|
|
// bitmap from the hardware, default to waveext...
|
|
mp_chmap_sel_add_waveext(&chmap_sel);
|
|
|
|
if (!ao_chmap_sel_adjust(ao, &chmap_sel, &ao->channels))
|
|
goto coreaudio_error;
|
|
|
|
} // closes if (!supports_digital)
|
|
|
|
// Build ASBD for the input format
|
|
AudioStreamBasicDescription asbd;
|
|
asbd.mSampleRate = ao->samplerate;
|
|
asbd.mFormatID = supports_digital ?
|
|
kAudioFormat60958AC3 : kAudioFormatLinearPCM;
|
|
asbd.mChannelsPerFrame = ao->channels.num;
|
|
asbd.mBitsPerChannel = af_fmt2bits(ao->format);
|
|
asbd.mFormatFlags = kAudioFormatFlagIsPacked;
|
|
|
|
if ((ao->format & AF_FORMAT_POINT_MASK) == AF_FORMAT_F)
|
|
asbd.mFormatFlags |= kAudioFormatFlagIsFloat;
|
|
|
|
if ((ao->format & AF_FORMAT_SIGN_MASK) == AF_FORMAT_SI)
|
|
asbd.mFormatFlags |= kAudioFormatFlagIsSignedInteger;
|
|
|
|
if ((ao->format & AF_FORMAT_END_MASK) == AF_FORMAT_BE)
|
|
asbd.mFormatFlags |= kAudioFormatFlagIsBigEndian;
|
|
|
|
asbd.mFramesPerPacket = 1;
|
|
asbd.mBytesPerPacket = asbd.mBytesPerFrame =
|
|
asbd.mFramesPerPacket * asbd.mChannelsPerFrame *
|
|
(asbd.mBitsPerChannel / 8);
|
|
|
|
ca_print_asbd(ao, "source format:", &asbd);
|
|
|
|
if (supports_digital)
|
|
return init_digital(ao, asbd);
|
|
else
|
|
return init_lpcm(ao, asbd);
|
|
|
|
coreaudio_error:
|
|
return CONTROL_ERROR;
|
|
}
|
|
|
|
static int init_lpcm(struct ao *ao, AudioStreamBasicDescription asbd)
|
|
{
|
|
OSStatus err;
|
|
uint32_t size;
|
|
struct priv *p = ao->priv;
|
|
|
|
AudioComponentDescription desc = (AudioComponentDescription) {
|
|
.componentType = kAudioUnitType_Output,
|
|
.componentSubType = (p->opt_device_id < 0) ?
|
|
kAudioUnitSubType_DefaultOutput :
|
|
kAudioUnitSubType_HALOutput,
|
|
.componentManufacturer = kAudioUnitManufacturer_Apple,
|
|
.componentFlags = 0,
|
|
.componentFlagsMask = 0,
|
|
};
|
|
|
|
AudioComponent comp = AudioComponentFindNext(NULL, &desc);
|
|
if (comp == NULL) {
|
|
MP_ERR(ao, "unable to find audio component\n");
|
|
goto coreaudio_error;
|
|
}
|
|
|
|
err = AudioComponentInstanceNew(comp, &(p->audio_unit));
|
|
CHECK_CA_ERROR("unable to open audio component");
|
|
|
|
// Initialize AudioUnit
|
|
err = AudioUnitInitialize(p->audio_unit);
|
|
CHECK_CA_ERROR_L(coreaudio_error_component,
|
|
"unable to initialize audio unit");
|
|
|
|
size = sizeof(AudioStreamBasicDescription);
|
|
err = AudioUnitSetProperty(p->audio_unit,
|
|
kAudioUnitProperty_StreamFormat,
|
|
kAudioUnitScope_Input, 0, &asbd, size);
|
|
|
|
CHECK_CA_ERROR_L(coreaudio_error_audiounit,
|
|
"unable to set the input format on the audio unit");
|
|
|
|
//Set the Current Device to the Default Output Unit.
|
|
err = AudioUnitSetProperty(p->audio_unit,
|
|
kAudioOutputUnitProperty_CurrentDevice,
|
|
kAudioUnitScope_Global, 0, &p->device,
|
|
sizeof(p->device));
|
|
CHECK_CA_ERROR_L(coreaudio_error_audiounit,
|
|
"can't link audio unit to selected device");
|
|
|
|
if (ao->channels.num > 2) {
|
|
// No need to set a channel layout for mono and stereo inputs
|
|
AudioChannelLayout acl = (AudioChannelLayout) {
|
|
.mChannelLayoutTag = kAudioChannelLayoutTag_UseChannelBitmap,
|
|
.mChannelBitmap = mp_chmap_to_waveext(&ao->channels)
|
|
};
|
|
|
|
err = AudioUnitSetProperty(p->audio_unit,
|
|
kAudioUnitProperty_AudioChannelLayout,
|
|
kAudioUnitScope_Input, 0, &acl,
|
|
sizeof(AudioChannelLayout));
|
|
CHECK_CA_ERROR_L(coreaudio_error_audiounit,
|
|
"can't set channel layout bitmap into audio unit");
|
|
}
|
|
|
|
p->buffer = mp_ring_new(p, get_ring_size(ao));
|
|
print_buffer(ao, p->buffer);
|
|
|
|
AURenderCallbackStruct render_cb = (AURenderCallbackStruct) {
|
|
.inputProc = render_cb_lpcm,
|
|
.inputProcRefCon = ao,
|
|
};
|
|
|
|
err = AudioUnitSetProperty(p->audio_unit,
|
|
kAudioUnitProperty_SetRenderCallback,
|
|
kAudioUnitScope_Input, 0, &render_cb,
|
|
sizeof(AURenderCallbackStruct));
|
|
|
|
CHECK_CA_ERROR_L(coreaudio_error_audiounit,
|
|
"unable to set render callback on audio unit");
|
|
|
|
reset(ao);
|
|
return CONTROL_OK;
|
|
|
|
coreaudio_error_audiounit:
|
|
AudioUnitUninitialize(p->audio_unit);
|
|
coreaudio_error_component:
|
|
AudioComponentInstanceDispose(p->audio_unit);
|
|
coreaudio_error:
|
|
return CONTROL_ERROR;
|
|
}
|
|
|
|
static int init_digital(struct ao *ao, AudioStreamBasicDescription asbd)
|
|
{
|
|
struct priv *p = ao->priv;
|
|
struct priv_d *d = p->digital;
|
|
OSStatus err = noErr;
|
|
|
|
uint32_t is_alive = 1;
|
|
err = CA_GET(p->device, kAudioDevicePropertyDeviceIsAlive, &is_alive);
|
|
CHECK_CA_WARN("could not check whether device is alive");
|
|
|
|
if (!is_alive)
|
|
MP_WARN(ao , "device is not alive\n");
|
|
|
|
p->is_digital = 1;
|
|
|
|
err = ca_lock_device(p->device, &d->hog_pid);
|
|
CHECK_CA_WARN("failed to set hogmode");
|
|
|
|
err = ca_disable_mixing(ao, p->device, &d->changed_mixing);
|
|
CHECK_CA_WARN("failed to disable mixing");
|
|
|
|
AudioStreamID *streams;
|
|
size_t n_streams;
|
|
|
|
/* Get a list of all the streams on this device. */
|
|
err = CA_GET_ARY_O(p->device, kAudioDevicePropertyStreams,
|
|
&streams, &n_streams);
|
|
|
|
CHECK_CA_ERROR("could not get number of streams");
|
|
|
|
for (int i = 0; i < n_streams && d->stream_idx < 0; i++) {
|
|
bool digital = ca_stream_supports_digital(ao, streams[i]);
|
|
|
|
if (digital) {
|
|
err = CA_GET(streams[i], kAudioStreamPropertyPhysicalFormat,
|
|
&d->original_asbd);
|
|
if (!CHECK_CA_WARN("could not get stream's physical format to "
|
|
"revert to, getting the next one"))
|
|
continue;
|
|
|
|
AudioStreamRangedDescription *formats;
|
|
size_t n_formats;
|
|
|
|
err = CA_GET_ARY(streams[i],
|
|
kAudioStreamPropertyAvailablePhysicalFormats,
|
|
&formats, &n_formats);
|
|
|
|
if (!CHECK_CA_WARN("could not get number of stream formats"))
|
|
continue; // try next one
|
|
|
|
int req_rate_format = -1;
|
|
int max_rate_format = -1;
|
|
|
|
d->stream = streams[i];
|
|
d->stream_idx = i;
|
|
|
|
for (int j = 0; j < n_formats; j++)
|
|
if (ca_format_is_digital(formats[j].mFormat)) {
|
|
// select the digital format that has exactly the same
|
|
// samplerate. If an exact match cannot be found, select
|
|
// the format with highest samplerate as backup.
|
|
if (formats[j].mFormat.mSampleRate == asbd.mSampleRate) {
|
|
req_rate_format = j;
|
|
break;
|
|
} else if (max_rate_format < 0 ||
|
|
formats[j].mFormat.mSampleRate >
|
|
formats[max_rate_format].mFormat.mSampleRate)
|
|
max_rate_format = j;
|
|
}
|
|
|
|
if (req_rate_format >= 0)
|
|
d->stream_asbd = formats[req_rate_format].mFormat;
|
|
else
|
|
d->stream_asbd = formats[max_rate_format].mFormat;
|
|
|
|
talloc_free(formats);
|
|
}
|
|
}
|
|
|
|
talloc_free(streams);
|
|
|
|
if (d->stream_idx < 0) {
|
|
MP_WARN(ao , "can't find any digital output stream format\n");
|
|
goto coreaudio_error;
|
|
}
|
|
|
|
if (!ca_change_format(ao, d->stream, d->stream_asbd))
|
|
goto coreaudio_error;
|
|
|
|
void *changed = (void *) &(d->stream_asbd_changed);
|
|
err = ca_enable_device_listener(p->device, changed);
|
|
CHECK_CA_ERROR("cannot install format change listener during init");
|
|
|
|
#if BYTE_ORDER == BIG_ENDIAN
|
|
if (!(p->stream_asdb.mFormatFlags & kAudioFormatFlagIsBigEndian))
|
|
#else
|
|
/* tell mplayer that we need a byteswap on AC3 streams, */
|
|
if (d->stream_asbd.mFormatID & kAudioFormat60958AC3)
|
|
ao->format = AF_FORMAT_AC3_LE;
|
|
else if (d->stream_asbd.mFormatFlags & kAudioFormatFlagIsBigEndian)
|
|
#endif
|
|
MP_WARN(ao, "stream has non-native byte order, output may fail\n");
|
|
|
|
ao->samplerate = d->stream_asbd.mSampleRate;
|
|
ao->bps = ao->samplerate *
|
|
(d->stream_asbd.mBytesPerPacket /
|
|
d->stream_asbd.mFramesPerPacket);
|
|
|
|
p->buffer = mp_ring_new(p, get_ring_size(ao));
|
|
print_buffer(ao, p->buffer);
|
|
|
|
err = AudioDeviceCreateIOProcID(p->device,
|
|
(AudioDeviceIOProc)render_cb_digital,
|
|
(void *)ao,
|
|
&d->render_cb);
|
|
|
|
CHECK_CA_ERROR("failed to register digital render callback");
|
|
|
|
reset(ao);
|
|
|
|
return CONTROL_TRUE;
|
|
|
|
coreaudio_error:
|
|
err = ca_unlock_device(p->device, &d->hog_pid);
|
|
CHECK_CA_WARN("can't release hog mode");
|
|
return CONTROL_ERROR;
|
|
}
|
|
|
|
static int play(struct ao *ao, void *output_samples, int num_bytes, int flags)
|
|
{
|
|
struct priv *p = ao->priv;
|
|
struct priv_d *d = p->digital;
|
|
|
|
// Check whether we need to reset the digital output stream.
|
|
if (p->is_digital && d->stream_asbd_changed) {
|
|
d->stream_asbd_changed = 0;
|
|
if (ca_stream_supports_digital(ao, d->stream)) {
|
|
if (!ca_change_format(ao, d->stream, d->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;
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
static float 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) / (float)ao->bps;
|
|
}
|
|
|
|
static void uninit(struct ao *ao, bool immed)
|
|
{
|
|
struct priv *p = ao->priv;
|
|
OSStatus err = noErr;
|
|
|
|
if (!immed)
|
|
mp_sleep_us(get_delay(ao) * 1000000);
|
|
|
|
if (!p->is_digital) {
|
|
AudioOutputUnitStop(p->audio_unit);
|
|
AudioUnitUninitialize(p->audio_unit);
|
|
AudioComponentInstanceDispose(p->audio_unit);
|
|
} else {
|
|
struct priv_d *d = p->digital;
|
|
|
|
void *changed = (void *) &(d->stream_asbd_changed);
|
|
err = ca_disable_device_listener(p->device, changed);
|
|
CHECK_CA_WARN("can't remove device listener, this may cause a crash");
|
|
|
|
err = AudioDeviceStop(p->device, d->render_cb);
|
|
CHECK_CA_WARN("failed to stop audio device");
|
|
|
|
err = AudioDeviceDestroyIOProcID(p->device, d->render_cb);
|
|
CHECK_CA_WARN("failed to remove device render callback");
|
|
|
|
if (!ca_change_format(ao, d->stream, d->original_asbd))
|
|
MP_WARN(ao, "can't revert to original device format");
|
|
|
|
err = ca_enable_mixing(ao, p->device, d->changed_mixing);
|
|
CHECK_CA_WARN("can't re-enable mixing");
|
|
|
|
err = ca_unlock_device(p->device, &d->hog_pid);
|
|
CHECK_CA_WARN("can't release hog mode");
|
|
}
|
|
}
|
|
|
|
static void audio_pause(struct ao *ao)
|
|
{
|
|
struct priv *p = ao->priv;
|
|
OSErr err = noErr;
|
|
|
|
if (p->paused)
|
|
return;
|
|
|
|
if (!p->is_digital) {
|
|
err = AudioOutputUnitStop(p->audio_unit);
|
|
CHECK_CA_WARN("can't stop audio unit");
|
|
} else {
|
|
struct priv_d *d = p->digital;
|
|
err = AudioDeviceStop(p->device, d->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;
|
|
OSErr err = noErr;
|
|
|
|
if (!p->paused)
|
|
return;
|
|
|
|
if (!p->is_digital) {
|
|
err = AudioOutputUnitStart(p->audio_unit);
|
|
CHECK_CA_WARN("can't start audio unit");
|
|
} else {
|
|
struct priv_d *d = p->digital;
|
|
err = AudioDeviceStart(p->device, d->render_cb);
|
|
CHECK_CA_WARN("can't start digital device");
|
|
}
|
|
|
|
p->paused = false;
|
|
}
|
|
|
|
#define OPT_BASE_STRUCT struct priv
|
|
|
|
const struct ao_driver audio_out_coreaudio = {
|
|
.info = &(const struct ao_info) {
|
|
"CoreAudio (OS X Audio Output)",
|
|
"coreaudio",
|
|
"Timothy J. Wood, Dan Christiansen, Chris Roccati & Stefano Pigozzi",
|
|
"",
|
|
},
|
|
.uninit = uninit,
|
|
.init = init,
|
|
.play = play,
|
|
.control = control,
|
|
.get_space = get_space,
|
|
.get_delay = get_delay,
|
|
.reset = reset,
|
|
.pause = audio_pause,
|
|
.resume = audio_resume,
|
|
.priv_size = sizeof(struct priv),
|
|
.options = (const struct m_option[]) {
|
|
OPT_INT("device_id", opt_device_id, 0, OPTDEF_INT(-1)),
|
|
OPT_FLAG("list", opt_list, 0),
|
|
{0}
|
|
},
|
|
};
|