2014-06-30 17:09:03 +00:00
|
|
|
/*
|
2024-02-20 20:24:54 +00:00
|
|
|
* CoreAudio audio output driver for macOS
|
2014-06-30 17:09:03 +00:00
|
|
|
*
|
|
|
|
* 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>
|
|
|
|
*
|
2015-04-13 07:36:54 +00:00
|
|
|
* This file is part of mpv.
|
2014-06-30 17:09:03 +00:00
|
|
|
*
|
2017-05-08 11:57:40 +00:00
|
|
|
* mpv is free software; you can redistribute it and/or
|
|
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
|
|
* License as published by the Free Software Foundation; either
|
|
|
|
* version 2.1 of the License, or (at your option) any later version.
|
2014-06-30 17:09:03 +00:00
|
|
|
*
|
2015-04-13 07:36:54 +00:00
|
|
|
* mpv is distributed in the hope that it will be useful,
|
2014-06-30 17:09:03 +00:00
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
2017-05-08 11:57:40 +00:00
|
|
|
* GNU Lesser General Public License for more details.
|
2014-06-30 17:09:03 +00:00
|
|
|
*
|
2017-05-08 11:57:40 +00:00
|
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
|
|
* License along with mpv. If not, see <http://www.gnu.org/licenses/>.
|
2014-06-30 17:09:03 +00:00
|
|
|
*/
|
|
|
|
|
|
|
|
/*
|
2024-02-20 20:24:54 +00:00
|
|
|
* The macOS CoreAudio framework doesn't mesh as simply as some
|
2014-06-30 17:09:03 +00:00
|
|
|
* 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).
|
|
|
|
*/
|
|
|
|
|
2023-10-19 14:26:26 +00:00
|
|
|
#include <stdatomic.h>
|
|
|
|
|
2015-03-10 09:34:41 +00:00
|
|
|
#include <CoreAudio/HostTime.h>
|
|
|
|
|
2017-06-29 10:35:58 +00:00
|
|
|
#include <libavutil/intreadwrite.h>
|
|
|
|
#include <libavutil/intfloat.h>
|
|
|
|
|
2014-06-30 17:09:03 +00:00
|
|
|
#include "ao.h"
|
|
|
|
#include "internal.h"
|
|
|
|
#include "audio/format.h"
|
|
|
|
#include "osdep/timer.h"
|
|
|
|
#include "options/m_option.h"
|
|
|
|
#include "common/msg.h"
|
2015-07-03 17:07:42 +00:00
|
|
|
#include "audio/out/ao_coreaudio_chmap.h"
|
2014-06-30 17:09:03 +00:00
|
|
|
#include "audio/out/ao_coreaudio_properties.h"
|
|
|
|
#include "audio/out/ao_coreaudio_utils.h"
|
2024-10-30 21:13:28 +00:00
|
|
|
#include "osdep/mac/compat.h"
|
2014-06-30 17:09:03 +00:00
|
|
|
|
|
|
|
struct priv {
|
2024-10-02 14:06:43 +00:00
|
|
|
// This must be put in the front
|
|
|
|
struct coreaudio_cb_sem sem;
|
|
|
|
|
2014-06-30 17:09:03 +00:00
|
|
|
AudioDeviceID device; // selected device
|
|
|
|
|
|
|
|
bool paused;
|
|
|
|
|
2015-05-06 16:54:53 +00:00
|
|
|
// audio render callback
|
2014-06-30 17:09:03 +00:00
|
|
|
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;
|
|
|
|
|
|
|
|
AudioStreamID stream;
|
|
|
|
|
|
|
|
// stream index in an AudioBufferList
|
|
|
|
int stream_idx;
|
|
|
|
|
2015-05-06 16:54:53 +00:00
|
|
|
// format we changed the stream to, and the original format to restore
|
2014-06-30 17:09:03 +00:00
|
|
|
AudioStreamBasicDescription stream_asbd;
|
|
|
|
AudioStreamBasicDescription original_asbd;
|
|
|
|
|
2017-06-29 10:35:58 +00:00
|
|
|
// Output s16 physical format, float32 virtual format, ac3/dts mpv format
|
2023-02-20 03:32:50 +00:00
|
|
|
bool spdif_hack;
|
2017-06-29 10:35:58 +00:00
|
|
|
|
2014-06-30 17:09:03 +00:00
|
|
|
bool changed_mixing;
|
2015-06-02 19:04:40 +00:00
|
|
|
|
|
|
|
atomic_bool reload_requested;
|
2014-06-30 17:09:03 +00:00
|
|
|
|
2023-10-11 18:45:40 +00:00
|
|
|
uint64_t hw_latency_ns;
|
2015-03-10 09:34:41 +00:00
|
|
|
};
|
2014-06-30 17:09:03 +00:00
|
|
|
|
2015-06-02 19:04:40 +00:00
|
|
|
static OSStatus property_listener_cb(
|
|
|
|
AudioObjectID object, uint32_t n_addresses,
|
|
|
|
const AudioObjectPropertyAddress addresses[],
|
|
|
|
void *data)
|
|
|
|
{
|
|
|
|
struct ao *ao = data;
|
|
|
|
struct priv *p = ao->priv;
|
|
|
|
|
|
|
|
// Check whether we need to reset the compressed output stream.
|
|
|
|
AudioStreamBasicDescription f;
|
2015-07-03 17:07:42 +00:00
|
|
|
OSErr err = CA_GET(p->stream, kAudioStreamPropertyVirtualFormat, &f);
|
2015-06-02 19:04:40 +00:00
|
|
|
CHECK_CA_WARN("could not get stream format");
|
|
|
|
if (err != noErr || !ca_asbd_equals(&p->stream_asbd, &f)) {
|
|
|
|
if (atomic_compare_exchange_strong(&p->reload_requested,
|
|
|
|
&(bool){false}, true))
|
|
|
|
{
|
|
|
|
ao_request_reload(ao);
|
|
|
|
MP_INFO(ao, "Stream format changed! Reloading.\n");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return noErr;
|
|
|
|
}
|
|
|
|
|
|
|
|
static OSStatus enable_property_listener(struct ao *ao, bool enabled)
|
|
|
|
{
|
|
|
|
struct priv *p = ao->priv;
|
|
|
|
|
2015-06-02 19:07:13 +00:00
|
|
|
uint32_t selectors[] = {kAudioDevicePropertyDeviceHasChanged,
|
|
|
|
kAudioHardwarePropertyDevices};
|
|
|
|
AudioDeviceID devs[] = {p->device,
|
|
|
|
kAudioObjectSystemObject};
|
2024-03-17 17:21:09 +00:00
|
|
|
static_assert(MP_ARRAY_SIZE(selectors) == MP_ARRAY_SIZE(devs), "");
|
2015-06-02 19:07:13 +00:00
|
|
|
|
|
|
|
OSStatus status = noErr;
|
|
|
|
for (int n = 0; n < MP_ARRAY_SIZE(devs); n++) {
|
|
|
|
AudioObjectPropertyAddress addr = {
|
|
|
|
.mScope = kAudioObjectPropertyScopeGlobal,
|
2024-10-30 21:13:28 +00:00
|
|
|
.mElement = kAudioObjectPropertyElementMain,
|
2015-06-02 19:07:13 +00:00
|
|
|
.mSelector = selectors[n],
|
|
|
|
};
|
|
|
|
AudioDeviceID device = devs[n];
|
|
|
|
|
|
|
|
OSStatus status2;
|
|
|
|
if (enabled) {
|
|
|
|
status2 = AudioObjectAddPropertyListener(
|
|
|
|
device, &addr, property_listener_cb, ao);
|
|
|
|
} else {
|
|
|
|
status2 = AudioObjectRemovePropertyListener(
|
|
|
|
device, &addr, property_listener_cb, ao);
|
|
|
|
}
|
|
|
|
if (status == noErr)
|
|
|
|
status = status2;
|
2015-06-02 19:04:40 +00:00
|
|
|
}
|
2015-06-02 19:07:13 +00:00
|
|
|
|
|
|
|
return status;
|
2015-06-02 19:04:40 +00:00
|
|
|
}
|
|
|
|
|
2017-06-29 10:35:58 +00:00
|
|
|
// This is a hack for passing through AC3/DTS on drivers which don't support it.
|
|
|
|
// The goal is to have the driver output the AC3 data bitexact, so basically we
|
|
|
|
// feed it float data by converting the AC3 data to float in the reverse way we
|
|
|
|
// assume the driver outputs it.
|
|
|
|
// Input: data_as_int16[0..samples]
|
|
|
|
// Output: data_as_float[0..samples]
|
|
|
|
// The conversion is done in-place.
|
|
|
|
static void bad_hack_mygodwhy(char *data, int samples)
|
|
|
|
{
|
|
|
|
// In reverse, so we can do it in-place.
|
|
|
|
for (int n = samples - 1; n >= 0; n--) {
|
|
|
|
int16_t val = AV_RN16(data + n * 2);
|
|
|
|
float fval = val / (float)(1 << 15);
|
|
|
|
uint32_t ival = av_float2int(fval);
|
|
|
|
AV_WN32(data + n * 4, ival);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-05-06 16:54:53 +00:00
|
|
|
static OSStatus render_cb_compressed(
|
2014-06-30 17:09:03 +00:00
|
|
|
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;
|
2017-06-29 10:35:58 +00:00
|
|
|
int sstride = p->spdif_hack ? 4 * ao->channels.num : ao->sstride;
|
2014-06-30 17:09:03 +00:00
|
|
|
|
2017-06-29 10:35:58 +00:00
|
|
|
int pseudo_frames = requested / sstride;
|
2015-03-10 09:34:41 +00:00
|
|
|
|
|
|
|
// we expect the callback to read full frames, which are aligned accordingly
|
2017-06-29 10:35:58 +00:00
|
|
|
if (pseudo_frames * sstride != requested) {
|
2015-03-10 09:34:41 +00:00
|
|
|
MP_ERR(ao, "Unsupported unaligned read of %d bytes.\n", requested);
|
|
|
|
return kAudioHardwareUnspecifiedError;
|
|
|
|
}
|
|
|
|
|
2023-10-11 18:45:40 +00:00
|
|
|
int64_t end = mp_time_ns();
|
|
|
|
end += p->hw_latency_ns + ca_get_latency(ts)
|
|
|
|
+ ca_frames_to_ns(ao, pseudo_frames);
|
2015-03-10 09:34:41 +00:00
|
|
|
|
2024-04-22 11:47:34 +00:00
|
|
|
ao_read_data(ao, &buf.mData, pseudo_frames, end, NULL, true, true);
|
2015-03-10 09:34:41 +00:00
|
|
|
|
2017-06-29 10:35:58 +00:00
|
|
|
if (p->spdif_hack)
|
|
|
|
bad_hack_mygodwhy(buf.mData, pseudo_frames * ao->channels.num);
|
|
|
|
|
2014-06-30 17:09:03 +00:00
|
|
|
return noErr;
|
|
|
|
}
|
|
|
|
|
2015-07-03 15:53:20 +00:00
|
|
|
// Apparently, audio devices can have multiple sub-streams. It's not clear to
|
|
|
|
// me what devices with multiple streams actually do. So only select the first
|
|
|
|
// one that fulfills some minimum requirements.
|
|
|
|
// If this is not sufficient, we could duplicate the device list entries for
|
|
|
|
// each sub-stream, and make it explicit.
|
|
|
|
static int select_stream(struct ao *ao)
|
|
|
|
{
|
|
|
|
struct priv *p = ao->priv;
|
|
|
|
|
|
|
|
AudioStreamID *streams;
|
|
|
|
size_t n_streams;
|
|
|
|
OSStatus err;
|
|
|
|
|
|
|
|
/* 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; i++) {
|
|
|
|
uint32_t direction;
|
|
|
|
err = CA_GET(streams[i], kAudioStreamPropertyDirection, &direction);
|
|
|
|
CHECK_CA_WARN("could not get stream direction");
|
|
|
|
if (err == noErr && direction != 0) {
|
|
|
|
MP_VERBOSE(ao, "Substream %d is not an output stream.\n", i);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2017-06-29 10:35:58 +00:00
|
|
|
if (af_fmt_is_pcm(ao->format) || p->spdif_hack ||
|
|
|
|
ca_stream_supports_compressed(ao, streams[i]))
|
2015-07-03 17:07:42 +00:00
|
|
|
{
|
2015-07-03 15:53:20 +00:00
|
|
|
MP_VERBOSE(ao, "Using substream %d/%zd.\n", i, n_streams);
|
|
|
|
p->stream = streams[i];
|
|
|
|
p->stream_idx = i;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
talloc_free(streams);
|
|
|
|
|
|
|
|
if (p->stream_idx < 0) {
|
|
|
|
MP_ERR(ao, "No useable substream found.\n");
|
|
|
|
goto coreaudio_error;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
coreaudio_error:
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2015-07-03 16:07:58 +00:00
|
|
|
static int find_best_format(struct ao *ao, AudioStreamBasicDescription *out_fmt)
|
|
|
|
{
|
|
|
|
struct priv *p = ao->priv;
|
|
|
|
|
|
|
|
// Build ASBD for the input format
|
|
|
|
AudioStreamBasicDescription asbd;
|
|
|
|
ca_fill_asbd(ao, &asbd);
|
2015-07-03 17:07:42 +00:00
|
|
|
ca_print_asbd(ao, "our format:", &asbd);
|
2015-07-03 16:07:58 +00:00
|
|
|
|
|
|
|
*out_fmt = (AudioStreamBasicDescription){0};
|
|
|
|
|
|
|
|
AudioStreamRangedDescription *formats;
|
|
|
|
size_t n_formats;
|
|
|
|
OSStatus err;
|
|
|
|
|
|
|
|
err = CA_GET_ARY(p->stream, kAudioStreamPropertyAvailablePhysicalFormats,
|
|
|
|
&formats, &n_formats);
|
|
|
|
CHECK_CA_ERROR("could not get number of stream formats");
|
|
|
|
|
|
|
|
for (int j = 0; j < n_formats; j++) {
|
|
|
|
AudioStreamBasicDescription *stream_asbd = &formats[j].mFormat;
|
|
|
|
|
|
|
|
ca_print_asbd(ao, "- ", stream_asbd);
|
|
|
|
|
|
|
|
if (!out_fmt->mFormatID || ca_asbd_is_better(&asbd, out_fmt, stream_asbd))
|
|
|
|
*out_fmt = *stream_asbd;
|
|
|
|
}
|
|
|
|
|
|
|
|
talloc_free(formats);
|
|
|
|
|
|
|
|
if (!out_fmt->mFormatID) {
|
|
|
|
MP_ERR(ao, "no format found\n");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
coreaudio_error:
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2014-06-30 17:09:03 +00:00
|
|
|
static int init(struct ao *ao)
|
|
|
|
{
|
|
|
|
struct priv *p = ao->priv;
|
2017-06-29 10:35:58 +00:00
|
|
|
int original_format = ao->format;
|
2014-06-30 17:09:03 +00:00
|
|
|
|
2014-10-12 10:11:32 +00:00
|
|
|
OSStatus err = ca_select_device(ao, ao->device, &p->device);
|
2015-05-06 16:46:51 +00:00
|
|
|
CHECK_CA_ERROR_L(coreaudio_error_nounlock, "failed to select device");
|
2014-06-30 17:09:03 +00:00
|
|
|
|
|
|
|
ao->format = af_fmt_from_planar(ao->format);
|
|
|
|
|
2015-07-03 17:07:42 +00:00
|
|
|
if (!af_fmt_is_pcm(ao->format) && !af_fmt_is_spdif(ao->format)) {
|
|
|
|
MP_ERR(ao, "Unsupported format.\n");
|
2015-05-06 16:46:51 +00:00
|
|
|
goto coreaudio_error_nounlock;
|
2015-05-06 16:51:31 +00:00
|
|
|
}
|
2014-06-30 17:09:03 +00:00
|
|
|
|
2017-06-29 10:35:58 +00:00
|
|
|
if (af_fmt_is_pcm(ao->format))
|
|
|
|
p->spdif_hack = false;
|
|
|
|
|
|
|
|
if (p->spdif_hack) {
|
|
|
|
if (af_fmt_to_bytes(ao->format) != 2) {
|
|
|
|
MP_ERR(ao, "HD formats not supported with spdif hack.\n");
|
|
|
|
goto coreaudio_error_nounlock;
|
|
|
|
}
|
|
|
|
// Let the pure evil begin!
|
|
|
|
ao->format = AF_FORMAT_S16;
|
|
|
|
}
|
|
|
|
|
2014-06-30 17:09:03 +00:00
|
|
|
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)
|
2017-06-29 10:35:58 +00:00
|
|
|
MP_WARN(ao, "device is not alive\n");
|
2014-06-30 17:09:03 +00:00
|
|
|
|
|
|
|
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");
|
|
|
|
|
2015-07-03 15:53:20 +00:00
|
|
|
if (select_stream(ao) < 0)
|
|
|
|
goto coreaudio_error;
|
2014-06-30 17:09:03 +00:00
|
|
|
|
2015-07-03 16:07:58 +00:00
|
|
|
AudioStreamBasicDescription hwfmt;
|
|
|
|
if (find_best_format(ao, &hwfmt) < 0)
|
|
|
|
goto coreaudio_error;
|
2014-06-30 17:09:03 +00:00
|
|
|
|
2015-04-28 20:11:43 +00:00
|
|
|
err = CA_GET(p->stream, kAudioStreamPropertyPhysicalFormat,
|
|
|
|
&p->original_asbd);
|
|
|
|
CHECK_CA_ERROR("could not get stream's original physical format");
|
|
|
|
|
2015-07-05 22:04:20 +00:00
|
|
|
// Even if changing the physical format fails, we can try using the current
|
|
|
|
// virtual format.
|
|
|
|
ca_change_physical_format_sync(ao, p->stream, hwfmt);
|
2014-06-30 17:09:03 +00:00
|
|
|
|
2015-07-03 17:07:42 +00:00
|
|
|
if (!ca_init_chmap(ao, p->device))
|
|
|
|
goto coreaudio_error;
|
|
|
|
|
|
|
|
err = CA_GET(p->stream, kAudioStreamPropertyVirtualFormat, &p->stream_asbd);
|
|
|
|
CHECK_CA_ERROR("could not get stream's virtual format");
|
|
|
|
|
|
|
|
ca_print_asbd(ao, "virtual format", &p->stream_asbd);
|
2014-06-30 17:09:03 +00:00
|
|
|
|
2015-10-26 15:00:17 +00:00
|
|
|
if (p->stream_asbd.mChannelsPerFrame > MP_NUM_CHANNELS) {
|
|
|
|
MP_ERR(ao, "unsupported number of channels: %d > %d.\n",
|
|
|
|
p->stream_asbd.mChannelsPerFrame, MP_NUM_CHANNELS);
|
|
|
|
goto coreaudio_error;
|
|
|
|
}
|
|
|
|
|
2015-07-03 17:07:42 +00:00
|
|
|
int new_format = ca_asbd_to_mp_format(&p->stream_asbd);
|
|
|
|
|
|
|
|
// If both old and new formats are spdif, avoid changing it due to the
|
|
|
|
// imperfect mapping between mp and CA formats.
|
|
|
|
if (!(af_fmt_is_spdif(ao->format) && af_fmt_is_spdif(new_format)))
|
|
|
|
ao->format = new_format;
|
|
|
|
|
|
|
|
if (!ao->format || af_fmt_is_planar(ao->format)) {
|
|
|
|
MP_ERR(ao, "hardware format not supported\n");
|
|
|
|
goto coreaudio_error;
|
|
|
|
}
|
2014-06-30 17:09:03 +00:00
|
|
|
|
|
|
|
ao->samplerate = p->stream_asbd.mSampleRate;
|
|
|
|
|
2015-07-03 17:07:42 +00:00
|
|
|
if (ao->channels.num != p->stream_asbd.mChannelsPerFrame) {
|
2015-10-21 20:18:41 +00:00
|
|
|
ca_get_active_chmap(ao, p->device, p->stream_asbd.mChannelsPerFrame,
|
|
|
|
&ao->channels);
|
2015-10-21 16:54:48 +00:00
|
|
|
}
|
|
|
|
if (!ao->channels.num) {
|
2015-07-03 17:07:42 +00:00
|
|
|
MP_ERR(ao, "number of channels changed, and unknown channel layout!\n");
|
|
|
|
goto coreaudio_error;
|
|
|
|
}
|
|
|
|
|
2017-06-29 10:35:58 +00:00
|
|
|
if (p->spdif_hack) {
|
|
|
|
AudioStreamBasicDescription physical_format = {0};
|
|
|
|
err = CA_GET(p->stream, kAudioStreamPropertyPhysicalFormat,
|
|
|
|
&physical_format);
|
|
|
|
CHECK_CA_ERROR("could not get stream's physical format");
|
|
|
|
int ph_format = ca_asbd_to_mp_format(&physical_format);
|
|
|
|
if (ao->format != AF_FORMAT_FLOAT || ph_format != AF_FORMAT_S16) {
|
|
|
|
MP_ERR(ao, "Wrong parameters for spdif hack (%d / %d)\n",
|
|
|
|
ao->format, ph_format);
|
|
|
|
}
|
|
|
|
ao->format = original_format; // pretend AC3 or DTS *evil laughter*
|
|
|
|
MP_WARN(ao, "Using spdif passthrough hack. This could produce noise.\n");
|
|
|
|
}
|
|
|
|
|
2023-10-11 18:45:40 +00:00
|
|
|
p->hw_latency_ns = ca_get_device_latency_ns(ao, p->device);
|
|
|
|
MP_VERBOSE(ao, "base latency: %lld nanoseconds\n", p->hw_latency_ns);
|
2014-06-30 17:09:03 +00:00
|
|
|
|
2015-07-03 17:07:42 +00:00
|
|
|
err = enable_property_listener(ao, true);
|
|
|
|
CHECK_CA_ERROR("cannot install format change listener during init");
|
|
|
|
|
2014-06-30 17:09:03 +00:00
|
|
|
err = AudioDeviceCreateIOProcID(p->device,
|
2015-05-06 16:54:53 +00:00
|
|
|
(AudioDeviceIOProc)render_cb_compressed,
|
2014-06-30 17:09:03 +00:00
|
|
|
(void *)ao,
|
|
|
|
&p->render_cb);
|
2015-05-06 16:54:53 +00:00
|
|
|
CHECK_CA_ERROR("failed to register audio render callback");
|
2014-06-30 17:09:03 +00:00
|
|
|
|
|
|
|
return CONTROL_TRUE;
|
|
|
|
|
|
|
|
coreaudio_error:
|
2015-06-02 19:04:40 +00:00
|
|
|
err = enable_property_listener(ao, false);
|
|
|
|
CHECK_CA_WARN("can't remove format change listener");
|
2014-06-30 17:09:03 +00:00
|
|
|
err = ca_unlock_device(p->device, &p->hog_pid);
|
|
|
|
CHECK_CA_WARN("can't release hog mode");
|
2015-05-06 16:46:51 +00:00
|
|
|
coreaudio_error_nounlock:
|
2014-06-30 17:09:03 +00:00
|
|
|
return CONTROL_ERROR;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void uninit(struct ao *ao)
|
|
|
|
{
|
|
|
|
struct priv *p = ao->priv;
|
|
|
|
OSStatus err = noErr;
|
|
|
|
|
2015-06-02 19:04:40 +00:00
|
|
|
err = enable_property_listener(ao, false);
|
2014-06-30 17:09:03 +00:00
|
|
|
CHECK_CA_WARN("can't remove device listener, this may cause a crash");
|
|
|
|
|
|
|
|
err = AudioDeviceStop(p->device, p->render_cb);
|
|
|
|
CHECK_CA_WARN("failed to stop audio device");
|
|
|
|
|
|
|
|
err = AudioDeviceDestroyIOProcID(p->device, p->render_cb);
|
|
|
|
CHECK_CA_WARN("failed to remove device render callback");
|
|
|
|
|
2015-06-02 19:13:44 +00:00
|
|
|
if (!ca_change_physical_format_sync(ao, p->stream, p->original_asbd))
|
2016-04-01 10:24:39 +00:00
|
|
|
MP_WARN(ao, "can't revert to original device format\n");
|
2014-06-30 17:09:03 +00:00
|
|
|
|
|
|
|
err = ca_enable_mixing(ao, p->device, p->changed_mixing);
|
|
|
|
CHECK_CA_WARN("can't re-enable mixing");
|
|
|
|
|
|
|
|
err = ca_unlock_device(p->device, &p->hog_pid);
|
|
|
|
CHECK_CA_WARN("can't release hog mode");
|
|
|
|
}
|
|
|
|
|
|
|
|
static void audio_pause(struct ao *ao)
|
|
|
|
{
|
|
|
|
struct priv *p = ao->priv;
|
|
|
|
|
|
|
|
OSStatus err = AudioDeviceStop(p->device, p->render_cb);
|
2015-05-06 16:54:53 +00:00
|
|
|
CHECK_CA_WARN("can't stop audio device");
|
2014-06-30 17:09:03 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static void audio_resume(struct ao *ao)
|
|
|
|
{
|
|
|
|
struct priv *p = ao->priv;
|
|
|
|
|
|
|
|
OSStatus err = AudioDeviceStart(p->device, p->render_cb);
|
2015-05-06 16:54:53 +00:00
|
|
|
CHECK_CA_WARN("can't start audio device");
|
2014-06-30 17:09:03 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#define OPT_BASE_STRUCT struct priv
|
|
|
|
|
|
|
|
const struct ao_driver audio_out_coreaudio_exclusive = {
|
|
|
|
.description = "CoreAudio Exclusive Mode",
|
|
|
|
.name = "coreaudio_exclusive",
|
|
|
|
.uninit = uninit,
|
|
|
|
.init = init,
|
2016-08-09 10:30:18 +00:00
|
|
|
.reset = audio_pause,
|
audio: redo internal AO API
This affects "pull" AOs only: ao_alsa, ao_pulse, ao_openal, ao_pcm,
ao_lavc. There are changes to the other AOs too, but that's only about
renaming ao_driver.resume to ao_driver.start.
ao_openal is broken because I didn't manage to fix it, so it exits with
an error message. If you want it, why don't _you_ put effort into it? I
see no reason to waste my own precious lifetime over this (I realize the
irony).
ao_alsa loses the poll() mechanism, but it was mostly broken and didn't
really do what it was supposed to. There doesn't seem to be anything in
the ALSA API to watch the playback status without polling (unless you
want to use raw UNIX signals).
No idea if ao_pulse is correct, or whether it's subtly broken now. There
is no documentation, so I can't tell what is correct, without reverse
engineering the whole project. I recommend using ALSA.
This was supposed to be just a simple fix, but somehow it expanded scope
like a train wreck. Very high chance of regressions, but probably only
for the AOs listed above. The rest you can figure out from reading the
diff.
2020-05-31 13:00:35 +00:00
|
|
|
.start = audio_resume,
|
2014-10-12 10:11:32 +00:00
|
|
|
.list_devs = ca_get_device_list,
|
2014-06-30 17:09:03 +00:00
|
|
|
.priv_size = sizeof(struct priv),
|
2014-08-25 08:05:49 +00:00
|
|
|
.priv_defaults = &(const struct priv){
|
2024-10-02 14:06:43 +00:00
|
|
|
.sem = (struct coreaudio_cb_sem){
|
|
|
|
.mutex = MP_STATIC_MUTEX_INITIALIZER,
|
|
|
|
.cond = MP_STATIC_COND_INITIALIZER,
|
|
|
|
},
|
2014-08-25 08:05:49 +00:00
|
|
|
.hog_pid = -1,
|
|
|
|
.stream = 0,
|
|
|
|
.stream_idx = -1,
|
2023-02-20 05:44:22 +00:00
|
|
|
.changed_mixing = false,
|
2014-08-25 08:05:49 +00:00
|
|
|
},
|
2017-06-29 10:35:58 +00:00
|
|
|
.options = (const struct m_option[]){
|
2023-02-20 03:32:50 +00:00
|
|
|
{"spdif-hack", OPT_BOOL(spdif_hack)},
|
2017-06-29 10:35:58 +00:00
|
|
|
{0}
|
|
|
|
},
|
|
|
|
.options_prefix = "coreaudio",
|
2014-06-30 17:09:03 +00:00
|
|
|
};
|