summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--audio/out/ao.c4
-rw-r--r--audio/out/ao_coreaudio.c510
-rw-r--r--audio/out/ao_coreaudio_device.c449
-rw-r--r--audio/out/ao_coreaudio_utils.c62
-rw-r--r--audio/out/ao_coreaudio_utils.h3
-rw-r--r--wscript_build.py1
6 files changed, 596 insertions, 433 deletions
diff --git a/audio/out/ao.c b/audio/out/ao.c
index 378197bcb3..977b8eb69a 100644
--- a/audio/out/ao.c
+++ b/audio/out/ao.c
@@ -38,6 +38,7 @@
extern const struct ao_driver audio_out_oss;
extern const struct ao_driver audio_out_coreaudio;
+extern const struct ao_driver audio_out_coreaudio_exclusive;
extern const struct ao_driver audio_out_rsound;
extern const struct ao_driver audio_out_sndio;
extern const struct ao_driver audio_out_pulse;
@@ -94,6 +95,9 @@ static const struct ao_driver * const audio_out_drivers[] = {
#if HAVE_ENCODING
&audio_out_lavc,
#endif
+#if HAVE_COREAUDIO
+ &audio_out_coreaudio_exclusive,
+#endif
#if HAVE_RSOUND
&audio_out_rsound,
#endif
diff --git a/audio/out/ao_coreaudio.c b/audio/out/ao_coreaudio.c
index 1d44c81124..63009f3314 100644
--- a/audio/out/ao_coreaudio.c
+++ b/audio/out/ao_coreaudio.c
@@ -1,38 +1,18 @@
/*
- * CoreAudio audio output driver for Mac OS X
+ * This file is part of mpv.
*
- * original copyright (C) Timothy J. Wood - Aug 2000
- * ported to MPlayer libao2 by Dan Christiansen
- *
- * Chris Roccati
- * Stefano Pigozzi
- *
- * 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
+ * mpv 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,
+ * mpv 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).
+ * with mpv. If not, see <http://www.gnu.org/licenses/>.
*/
#include "config.h"
@@ -50,47 +30,11 @@ 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;
@@ -124,77 +68,29 @@ static OSStatus render_cb_lpcm(void *ctx, AudioUnitRenderActionFlags *aflags,
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 digitalvol = d->muted ? 0 : 100;
- *control_vol = (ao_control_vol_t) {
- .left = digitalvol, .right = digitalvol,
- };
- return CONTROL_TRUE;
- }
-
- err = AudioUnitGetParameter(p->audio_unit, kHALOutputParam_Volume,
- kAudioUnitScope_Global, 0, &vol);
-
+ case AOCONTROL_GET_VOLUME: {
+ ao_control_vol_t *control_vol = (ao_control_vol_t *)arg;
+ float vol;
+ OSStatus 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);
-
+ }
+ case AOCONTROL_SET_VOLUME: {
+ ao_control_vol_t *control_vol = (ao_control_vol_t *)arg;
+ float vol = (control_vol->left + control_vol->right) / 200.0;
+ OSStatus 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;
@@ -202,136 +98,30 @@ 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;
- 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 bool init_chmap(struct ao *ao);
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,
- };
+ struct priv *p = ao->priv;
- p->digital = d;
+ if (p->opt_list) ca_print_device_list(ao);
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;
- }
-
- if (mp_msg_test(ao->log, MSGL_V)) {
- char *name;
- err = CA_GET_STR(selected_device, kAudioObjectPropertyName, &name);
- CHECK_CA_ERROR("could not get selected audio device name");
+ OSStatus err = ca_select_device(ao, p->opt_device_id, &p->device);
+ CHECK_CA_ERROR("failed to select device");
- MP_VERBOSE(ao, "selected audio output device: %s (%" PRIu32 ")\n",
- name, selected_device);
-
- talloc_free(name);
- }
-
- // Save selected device id
- p->device = selected_device;
+ if (!init_chmap(ao))
+ goto coreaudio_error;
ao->format = af_fmt_from_planar(ao->format);
- 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");
-
- struct mp_chmap_sel chmap_sel = {0};
- for (int i = 0; i < n_layouts; i++) {
- struct mp_chmap chmap = {0};
- if (ca_layout_to_mp_chmap(ao, &layouts[i], &chmap))
- mp_chmap_sel_add_map(&chmap_sel, &chmap);
- }
-
- talloc_free(layouts);
-
- if (ao->channels.num < 3) {
- struct mp_chmap chmap;
- mp_chmap_from_channels(&chmap, ao->channels.num);
- mp_chmap_sel_add_map(&chmap_sel, &chmap);
- }
-
- if (!ao_chmap_sel_adjust(ao, &chmap_sel, &ao->channels)) {
- MP_ERR(ao, "could not select a suitable channel map among the "
- "hardware supported ones. Make sure to configure your "
- "output device correctly in 'Audio MIDI Setup.app'\n");
- 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.mFormatID = kAudioFormatLinearPCM;
asbd.mChannelsPerFrame = ao->channels.num;
asbd.mBitsPerChannel = af_fmt2bits(ao->format);
asbd.mFormatFlags = kAudioFormatFlagIsPacked;
@@ -352,15 +142,52 @@ static int init(struct ao *ao)
ca_print_asbd(ao, "source format:", &asbd);
- if (supports_digital)
- return init_digital(ao, asbd);
- else
- return init_lpcm(ao, asbd);
+ return init_lpcm(ao, asbd);
coreaudio_error:
return CONTROL_ERROR;
}
+static bool init_chmap(struct ao *ao)
+{
+ struct priv *p = ao->priv;
+ OSStatus err;
+ AudioChannelLayout *layouts;
+ size_t n_layouts;
+
+ err = CA_GET_ARY_O(p->device,
+ kAudioDevicePropertyPreferredChannelLayout,
+ &layouts, &n_layouts);
+ CHECK_CA_ERROR("could not get audio device prefered layouts");
+
+ struct mp_chmap_sel chmap_sel = {0};
+ for (int i = 0; i < n_layouts; i++) {
+ struct mp_chmap chmap = {0};
+ if (ca_layout_to_mp_chmap(ao, &layouts[i], &chmap))
+ mp_chmap_sel_add_map(&chmap_sel, &chmap);
+ }
+
+ talloc_free(layouts);
+
+ if (ao->channels.num < 3) {
+ struct mp_chmap chmap;
+ mp_chmap_from_channels(&chmap, ao->channels.num);
+ mp_chmap_sel_add_map(&chmap_sel, &chmap);
+ }
+
+ if (!ao_chmap_sel_adjust(ao, &chmap_sel, &ao->channels)) {
+ MP_ERR(ao, "could not select a suitable channel map among the "
+ "hardware supported ones. Make sure to configure your "
+ "output device correctly in 'Audio MIDI Setup.app'\n");
+ goto coreaudio_error;
+ }
+
+ return true;
+
+coreaudio_error:
+ return false;
+}
+
static int init_lpcm(struct ao *ao, AudioStreamBasicDescription asbd)
{
OSStatus err;
@@ -408,8 +235,6 @@ static int init_lpcm(struct ao *ao, AudioStreamBasicDescription asbd)
"can't link audio unit to selected device");
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,
@@ -434,153 +259,11 @@ 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 **data, int samples, int flags)
{
- struct priv *p = ao->priv;
- struct priv_d *d = p->digital;
+ 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->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 num_bytes = samples * ao->sstride;
int wrote = mp_ring_write(p->buffer, output_samples, num_bytes);
audio_resume(ao);
@@ -612,52 +295,20 @@ static float get_delay(struct ao *ao)
static void uninit(struct ao *ao)
{
struct priv *p = ao->priv;
- OSStatus err = noErr;
-
- 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");
- }
+ AudioOutputUnitStop(p->audio_unit);
+ AudioUnitUninitialize(p->audio_unit);
+ AudioComponentInstanceDispose(p->audio_unit);
}
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");
- }
+ OSStatus err = AudioOutputUnitStop(p->audio_unit);
+ CHECK_CA_WARN("can't stop audio unit");
p->paused = true;
}
@@ -665,19 +316,12 @@ static void audio_pause(struct ao *ao)
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");
- }
+ OSStatus err = AudioOutputUnitStart(p->audio_unit);
+ CHECK_CA_WARN("can't start audio unit");
p->paused = false;
}
@@ -685,7 +329,7 @@ static void audio_resume(struct ao *ao)
#define OPT_BASE_STRUCT struct priv
const struct ao_driver audio_out_coreaudio = {
- .description = "CoreAudio (OS X Audio Output)",
+ .description = "CoreAudio AudioUnit",
.name = "coreaudio",
.uninit = uninit,
.init = init,
diff --git a/audio/out/ao_coreaudio_device.c b/audio/out/ao_coreaudio_device.c
new file mode 100644
index 0000000000..c86ffbd700
--- /dev/null
+++ b/audio/out/ao_coreaudio_device.c
@@ -0,0 +1,449 @@
+/*
+ * CoreAudio audio output driver for Mac OS X
+ *
+ * original copyright (C) Timothy J. Wood - Aug 2000
+ * ported to MPlayer libao2 by Dan Christiansen
+ *
+ * Chris Roccati
+ * Stefano Pigozzi
+ *
+ * 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 "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);
+
+struct priv {
+ AudioDeviceID device; // selected device
+
+ bool paused;
+
+ struct mp_ring *buffer;
+
+ // 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;
+
+ // 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_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;
+ AudioBuffer buf = out_data->mBuffers[p->stream_idx];
+ int requested = buf.mDataByteSize;
+
+ if (p->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;
+ switch (cmd) {
+ case AOCONTROL_GET_VOLUME:
+ control_vol = (ao_control_vol_t *)arg;
+ // Digital output has no volume adjust.
+ int digitalvol = p->muted ? 0 : 100;
+ *control_vol = (ao_control_vol_t) {
+ .left = digitalvol, .right = digitalvol,
+ };
+ return CONTROL_TRUE;
+
+ case AOCONTROL_SET_VOLUME:
+ control_vol = (ao_control_vol_t *)arg;
+ // 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)
+ p->muted = true;
+ else
+ p->muted = false;
+ return CONTROL_TRUE;
+
+ } // end switch
+ return CONTROL_UNKNOWN;
+}
+
+static int init_digital(struct ao *ao, AudioStreamBasicDescription asbd);
+
+static int init(struct ao *ao)
+{
+ struct priv *p = ao->priv;
+
+ if (p->opt_list) ca_print_device_list(ao);
+
+ *p = (struct priv) {
+ .muted = false,
+ .stream_asbd_changed = 0,
+ .hog_pid = -1,
+ .stream = 0,
+ .stream_idx = -1,
+ .changed_mixing = false,
+ };
+
+ OSStatus err = ca_select_device(ao, p->opt_device_id, &p->device);
+ CHECK_CA_ERROR("failed to select device");
+
+ ao->format = af_fmt_from_planar(ao->format);
+
+ 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, p->device))
+ supports_digital = true;
+ }
+
+ if (!supports_digital) {
+ MP_ERR(ao, "selected device doesn't support digital formats\n");
+ goto coreaudio_error;
+ } // closes if (!supports_digital)
+
+ // Build ASBD for the input format
+ AudioStreamBasicDescription asbd;
+ asbd.mSampleRate = ao->samplerate;
+ asbd.mFormatID = kAudioFormat60958AC3;
+ 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);
+
+ return init_digital(ao, asbd);
+
+coreaudio_error:
+ return CONTROL_ERROR;
+}
+
+static int init_digital(struct ao *ao, AudioStreamBasicDescription asbd)
+{
+ struct priv *p = ao->priv;
+ 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");
+
+ err = ca_lock_device(p->device, &p->hog_pid);
+ CHECK_CA_WARN("failed to set hogmode");
+
+ err = ca_disable_mixing(ao, p->device, &p->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 && p->stream_idx < 0; i++) {
+ bool digital = ca_stream_supports_digital(ao, streams[i]);
+
+ if (digital) {
+ err = CA_GET(streams[i], kAudioStreamPropertyPhysicalFormat,
+ &p->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;
+
+ p->stream = streams[i];
+ p->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)
+ p->stream_asbd = formats[req_rate_format].mFormat;
+ else
+ p->stream_asbd = formats[max_rate_format].mFormat;
+
+ talloc_free(formats);
+ }
+ }
+
+ talloc_free(streams);
+
+ if (p->stream_idx < 0) {
+ MP_WARN(ao , "can't find any digital output stream format\n");
+ goto coreaudio_error;
+ }
+
+ if (!ca_change_format(ao, p->stream, p->stream_asbd))
+ goto coreaudio_error;
+
+ void *changed = (void *) &(p->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 (p->stream_asbd.mFormatID & kAudioFormat60958AC3)
+ ao->format = AF_FORMAT_AC3_LE;
+ else if (p->stream_asbd.mFormatFlags & kAudioFormatFlagIsBigEndian)
+#endif
+ MP_WARN(ao, "stream has non-native byte order, output may fail\n");
+
+ ao->samplerate = p->stream_asbd.mSampleRate;
+ ao->bps = ao->samplerate *
+ (p->stream_asbd.mBytesPerPacket /
+ p->stream_asbd.mFramesPerPacket);
+
+ p->buffer = mp_ring_new(p, get_ring_size(ao));
+
+ 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:
+ err = ca_unlock_device(p->device, &p->hog_pid);
+ CHECK_CA_WARN("can't release hog mode");
+ return CONTROL_ERROR;
+}
+
+static int play(struct ao *ao, void **data, int sam