summaryrefslogtreecommitdiffstats
path: root/audio/out/ao_coreaudio_exclusive.c
diff options
context:
space:
mode:
Diffstat (limited to 'audio/out/ao_coreaudio_exclusive.c')
-rw-r--r--audio/out/ao_coreaudio_exclusive.c94
1 files changed, 77 insertions, 17 deletions
diff --git a/audio/out/ao_coreaudio_exclusive.c b/audio/out/ao_coreaudio_exclusive.c
index 6a657ff807..5e0ec3b19f 100644
--- a/audio/out/ao_coreaudio_exclusive.c
+++ b/audio/out/ao_coreaudio_exclusive.c
@@ -1,5 +1,5 @@
/*
- * CoreAudio audio output driver for Mac OS X
+ * CoreAudio audio output driver for macOS
*
* original copyright (C) Timothy J. Wood - Aug 2000
* ported to MPlayer libao2 by Dan Christiansen
@@ -28,20 +28,23 @@
*/
/*
- * The MacOS X CoreAudio framework doesn't mesh as simply as some
+ * The macOS 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 <stdatomic.h>
+
#include <CoreAudio/HostTime.h>
-#include "config.h"
+#include <libavutil/intreadwrite.h>
+#include <libavutil/intfloat.h>
+
#include "ao.h"
#include "internal.h"
#include "audio/format.h"
#include "osdep/timer.h"
-#include "osdep/atomic.h"
#include "options/m_option.h"
#include "common/msg.h"
#include "audio/out/ao_coreaudio_chmap.h"
@@ -69,11 +72,14 @@ struct priv {
AudioStreamBasicDescription stream_asbd;
AudioStreamBasicDescription original_asbd;
+ // Output s16 physical format, float32 virtual format, ac3/dts mpv format
+ bool spdif_hack;
+
bool changed_mixing;
atomic_bool reload_requested;
- uint32_t hw_latency_us;
+ uint64_t hw_latency_ns;
};
static OSStatus property_listener_cb(
@@ -108,7 +114,7 @@ static OSStatus enable_property_listener(struct ao *ao, bool enabled)
kAudioHardwarePropertyDevices};
AudioDeviceID devs[] = {p->device,
kAudioObjectSystemObject};
- assert(MP_ARRAY_SIZE(selectors) == MP_ARRAY_SIZE(devs));
+ static_assert(MP_ARRAY_SIZE(selectors) == MP_ARRAY_SIZE(devs), "");
OSStatus status = noErr;
for (int n = 0; n < MP_ARRAY_SIZE(devs); n++) {
@@ -134,6 +140,24 @@ static OSStatus enable_property_listener(struct ao *ao, bool enabled)
return status;
}
+// 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);
+ }
+}
+
static OSStatus render_cb_compressed(
AudioDeviceID device, const AudioTimeStamp *ts,
const void *in_data, const AudioTimeStamp *in_ts,
@@ -143,21 +167,25 @@ static OSStatus render_cb_compressed(
struct priv *p = ao->priv;
AudioBuffer buf = out_data->mBuffers[p->stream_idx];
int requested = buf.mDataByteSize;
+ int sstride = p->spdif_hack ? 4 * ao->channels.num : ao->sstride;
- int pseudo_frames = requested / ao->sstride;
+ int pseudo_frames = requested / sstride;
// we expect the callback to read full frames, which are aligned accordingly
- if (pseudo_frames * ao->sstride != requested) {
+ if (pseudo_frames * 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);
+ int64_t end = mp_time_ns();
+ end += p->hw_latency_ns + ca_get_latency(ts)
+ + ca_frames_to_ns(ao, pseudo_frames);
ao_read_data(ao, &buf.mData, pseudo_frames, end);
+ if (p->spdif_hack)
+ bad_hack_mygodwhy(buf.mData, pseudo_frames * ao->channels.num);
+
return noErr;
}
@@ -187,8 +215,8 @@ static int select_stream(struct ao *ao)
continue;
}
- if (af_fmt_is_pcm(ao->format) || ca_stream_supports_compressed(ao,
- streams[i]))
+ if (af_fmt_is_pcm(ao->format) || p->spdif_hack ||
+ ca_stream_supports_compressed(ao, streams[i]))
{
MP_VERBOSE(ao, "Using substream %d/%zd.\n", i, n_streams);
p->stream = streams[i];
@@ -253,6 +281,7 @@ coreaudio_error:
static int init(struct ao *ao)
{
struct priv *p = ao->priv;
+ int original_format = ao->format;
OSStatus err = ca_select_device(ao, ao->device, &p->device);
CHECK_CA_ERROR_L(coreaudio_error_nounlock, "failed to select device");
@@ -264,12 +293,24 @@ static int init(struct ao *ao)
goto coreaudio_error_nounlock;
}
+ 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;
+ }
+
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");
+ MP_WARN(ao, "device is not alive\n");
err = ca_lock_device(p->device, &p->hog_pid);
CHECK_CA_WARN("failed to set hogmode");
@@ -329,8 +370,22 @@ static int init(struct ao *ao)
goto coreaudio_error;
}
- p->hw_latency_us = ca_get_device_latency_us(ao, p->device);
- MP_VERBOSE(ao, "base latency: %d microseconds\n", (int)p->hw_latency_us);
+ 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");
+ }
+
+ p->hw_latency_ns = ca_get_device_latency_ns(ao, p->device);
+ MP_VERBOSE(ao, "base latency: %lld nanoseconds\n", p->hw_latency_ns);
err = enable_property_listener(ao, true);
CHECK_CA_ERROR("cannot install format change listener during init");
@@ -400,7 +455,7 @@ const struct ao_driver audio_out_coreaudio_exclusive = {
.uninit = uninit,
.init = init,
.reset = audio_pause,
- .resume = audio_resume,
+ .start = audio_resume,
.list_devs = ca_get_device_list,
.priv_size = sizeof(struct priv),
.priv_defaults = &(const struct priv){
@@ -409,4 +464,9 @@ const struct ao_driver audio_out_coreaudio_exclusive = {
.stream_idx = -1,
.changed_mixing = false,
},
+ .options = (const struct m_option[]){
+ {"spdif-hack", OPT_BOOL(spdif_hack)},
+ {0}
+ },
+ .options_prefix = "coreaudio",
};