1 /*
2  * This file is part of mpv.
3  *
4  * mpv is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public
6  * License as published by the Free Software Foundation; either
7  * version 2.1 of the License, or (at your option) any later version.
8  *
9  * mpv is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with mpv.  If not, see <http://www.gnu.org/licenses/>.
16  */
17 
18 #include <CoreAudio/HostTime.h>
19 
20 #include "config.h"
21 #include "ao.h"
22 #include "internal.h"
23 #include "audio/format.h"
24 #include "osdep/timer.h"
25 #include "options/m_option.h"
26 #include "common/msg.h"
27 #include "ao_coreaudio_chmap.h"
28 #include "ao_coreaudio_properties.h"
29 #include "ao_coreaudio_utils.h"
30 
31 struct priv {
32     AudioDeviceID device;
33     AudioUnit audio_unit;
34 
35     uint64_t hw_latency_us;
36 
37     AudioStreamBasicDescription original_asbd;
38     AudioStreamID original_asbd_stream;
39 
40     int change_physical_format;
41 };
42 
ca_get_hardware_latency(struct ao * ao)43 static int64_t ca_get_hardware_latency(struct ao *ao) {
44     struct priv *p = ao->priv;
45 
46     double audiounit_latency_sec = 0.0;
47     uint32_t size = sizeof(audiounit_latency_sec);
48     OSStatus err = AudioUnitGetProperty(
49             p->audio_unit,
50             kAudioUnitProperty_Latency,
51             kAudioUnitScope_Global,
52             0,
53             &audiounit_latency_sec,
54             &size);
55     CHECK_CA_ERROR("cannot get audio unit latency");
56 
57     uint64_t audiounit_latency_us = audiounit_latency_sec * 1e6;
58     uint64_t device_latency_us    = ca_get_device_latency_us(ao, p->device);
59 
60     MP_VERBOSE(ao, "audiounit latency [us]: %lld\n", audiounit_latency_us);
61     MP_VERBOSE(ao, "device latency [us]: %lld\n", device_latency_us);
62 
63     return audiounit_latency_us + device_latency_us;
64 
65 coreaudio_error:
66     return 0;
67 }
68 
render_cb_lpcm(void * ctx,AudioUnitRenderActionFlags * aflags,const AudioTimeStamp * ts,UInt32 bus,UInt32 frames,AudioBufferList * buffer_list)69 static OSStatus render_cb_lpcm(void *ctx, AudioUnitRenderActionFlags *aflags,
70                               const AudioTimeStamp *ts, UInt32 bus,
71                               UInt32 frames, AudioBufferList *buffer_list)
72 {
73     struct ao *ao   = ctx;
74     struct priv *p  = ao->priv;
75     void *planes[MP_NUM_CHANNELS] = {0};
76 
77     for (int n = 0; n < ao->num_planes; n++)
78         planes[n] = buffer_list->mBuffers[n].mData;
79 
80     int64_t end = mp_time_us();
81     end += p->hw_latency_us + ca_get_latency(ts) + ca_frames_to_us(ao, frames);
82     ao_read_data(ao, planes, frames, end);
83     return noErr;
84 }
85 
get_volume(struct ao * ao,struct ao_control_vol * vol)86 static int get_volume(struct ao *ao, struct ao_control_vol *vol) {
87     struct priv *p = ao->priv;
88     float auvol;
89     OSStatus err =
90         AudioUnitGetParameter(p->audio_unit, kHALOutputParam_Volume,
91                               kAudioUnitScope_Global, 0, &auvol);
92 
93     CHECK_CA_ERROR("could not get HAL output volume");
94     vol->left = vol->right = auvol * 100.0;
95     return CONTROL_TRUE;
96 coreaudio_error:
97     return CONTROL_ERROR;
98 }
99 
set_volume(struct ao * ao,struct ao_control_vol * vol)100 static int set_volume(struct ao *ao, struct ao_control_vol *vol) {
101     struct priv *p = ao->priv;
102     float auvol = (vol->left + vol->right) / 200.0;
103     OSStatus err =
104         AudioUnitSetParameter(p->audio_unit, kHALOutputParam_Volume,
105                               kAudioUnitScope_Global, 0, auvol, 0);
106     CHECK_CA_ERROR("could not set HAL output volume");
107     return CONTROL_TRUE;
108 coreaudio_error:
109     return CONTROL_ERROR;
110 }
111 
control(struct ao * ao,enum aocontrol cmd,void * arg)112 static int control(struct ao *ao, enum aocontrol cmd, void *arg)
113 {
114     switch (cmd) {
115     case AOCONTROL_GET_VOLUME:
116         return get_volume(ao, arg);
117     case AOCONTROL_SET_VOLUME:
118         return set_volume(ao, arg);
119     }
120     return CONTROL_UNKNOWN;
121 }
122 
123 static bool init_audiounit(struct ao *ao, AudioStreamBasicDescription asbd);
124 static void init_physical_format(struct ao *ao);
125 
reinit_device(struct ao * ao)126 static bool reinit_device(struct ao *ao) {
127     struct priv *p = ao->priv;
128 
129     OSStatus err = ca_select_device(ao, ao->device, &p->device);
130     CHECK_CA_ERROR("failed to select device");
131 
132     return true;
133 
134 coreaudio_error:
135     return false;
136 }
137 
init(struct ao * ao)138 static int init(struct ao *ao)
139 {
140     struct priv *p = ao->priv;
141 
142     if (!af_fmt_is_pcm(ao->format) || (ao->init_flags & AO_INIT_EXCLUSIVE)) {
143         MP_VERBOSE(ao, "redirecting to coreaudio_exclusive\n");
144         ao->redirect = "coreaudio_exclusive";
145         return CONTROL_ERROR;
146     }
147 
148     if (!reinit_device(ao))
149         goto coreaudio_error;
150 
151     if (p->change_physical_format)
152         init_physical_format(ao);
153 
154     if (!ca_init_chmap(ao, p->device))
155         goto coreaudio_error;
156 
157     AudioStreamBasicDescription asbd;
158     ca_fill_asbd(ao, &asbd);
159 
160     if (!init_audiounit(ao, asbd))
161         goto coreaudio_error;
162 
163     return CONTROL_OK;
164 
165 coreaudio_error:
166     return CONTROL_ERROR;
167 }
168 
init_physical_format(struct ao * ao)169 static void init_physical_format(struct ao *ao)
170 {
171     struct priv *p = ao->priv;
172     OSErr err;
173 
174     void *tmp = talloc_new(NULL);
175 
176     AudioStreamBasicDescription asbd;
177     ca_fill_asbd(ao, &asbd);
178 
179     AudioStreamID *streams;
180     size_t n_streams;
181 
182     err = CA_GET_ARY_O(p->device, kAudioDevicePropertyStreams,
183                        &streams, &n_streams);
184     CHECK_CA_ERROR("could not get number of streams");
185 
186     talloc_steal(tmp, streams);
187 
188     MP_VERBOSE(ao, "Found %zd substream(s).\n", n_streams);
189 
190     for (int i = 0; i < n_streams; i++) {
191         AudioStreamRangedDescription *formats;
192         size_t n_formats;
193 
194         MP_VERBOSE(ao, "Looking at formats in substream %d...\n", i);
195 
196         err = CA_GET_ARY(streams[i], kAudioStreamPropertyAvailablePhysicalFormats,
197                          &formats, &n_formats);
198 
199         if (!CHECK_CA_WARN("could not get number of stream formats"))
200             continue; // try next one
201 
202         talloc_steal(tmp, formats);
203 
204         uint32_t direction;
205         err = CA_GET(streams[i], kAudioStreamPropertyDirection, &direction);
206         CHECK_CA_ERROR("could not get stream direction");
207         if (direction != 0) {
208             MP_VERBOSE(ao, "Not an output stream.\n");
209             continue;
210         }
211 
212         AudioStreamBasicDescription best_asbd = {0};
213 
214         for (int j = 0; j < n_formats; j++) {
215             AudioStreamBasicDescription *stream_asbd = &formats[j].mFormat;
216 
217             ca_print_asbd(ao, "- ", stream_asbd);
218 
219             if (!best_asbd.mFormatID || ca_asbd_is_better(&asbd, &best_asbd,
220                                                           stream_asbd))
221                 best_asbd = *stream_asbd;
222         }
223 
224         if (best_asbd.mFormatID) {
225             p->original_asbd_stream = streams[i];
226             err = CA_GET(p->original_asbd_stream,
227                          kAudioStreamPropertyPhysicalFormat,
228                          &p->original_asbd);
229             CHECK_CA_WARN("could not get current physical stream format");
230 
231             if (ca_asbd_equals(&p->original_asbd, &best_asbd)) {
232                 MP_VERBOSE(ao, "Requested format already set, not changing.\n");
233                 p->original_asbd.mFormatID = 0;
234                 break;
235             }
236 
237             if (!ca_change_physical_format_sync(ao, streams[i], best_asbd))
238                 p->original_asbd = (AudioStreamBasicDescription){0};
239             break;
240         }
241     }
242 
243 coreaudio_error:
244     talloc_free(tmp);
245     return;
246 }
247 
init_audiounit(struct ao * ao,AudioStreamBasicDescription asbd)248 static bool init_audiounit(struct ao *ao, AudioStreamBasicDescription asbd)
249 {
250     OSStatus err;
251     uint32_t size;
252     struct priv *p = ao->priv;
253 
254     AudioComponentDescription desc = (AudioComponentDescription) {
255         .componentType         = kAudioUnitType_Output,
256         .componentSubType      = (ao->device) ?
257                                     kAudioUnitSubType_HALOutput :
258                                     kAudioUnitSubType_DefaultOutput,
259         .componentManufacturer = kAudioUnitManufacturer_Apple,
260         .componentFlags        = 0,
261         .componentFlagsMask    = 0,
262     };
263 
264     AudioComponent comp = AudioComponentFindNext(NULL, &desc);
265     if (comp == NULL) {
266         MP_ERR(ao, "unable to find audio component\n");
267         goto coreaudio_error;
268     }
269 
270     err = AudioComponentInstanceNew(comp, &(p->audio_unit));
271     CHECK_CA_ERROR("unable to open audio component");
272 
273     err = AudioUnitInitialize(p->audio_unit);
274     CHECK_CA_ERROR_L(coreaudio_error_component,
275                      "unable to initialize audio unit");
276 
277     size = sizeof(AudioStreamBasicDescription);
278     err = AudioUnitSetProperty(p->audio_unit,
279                                kAudioUnitProperty_StreamFormat,
280                                kAudioUnitScope_Input, 0, &asbd, size);
281 
282     CHECK_CA_ERROR_L(coreaudio_error_audiounit,
283                      "unable to set the input format on the audio unit");
284 
285     err = AudioUnitSetProperty(p->audio_unit,
286                                kAudioOutputUnitProperty_CurrentDevice,
287                                kAudioUnitScope_Global, 0, &p->device,
288                                sizeof(p->device));
289     CHECK_CA_ERROR_L(coreaudio_error_audiounit,
290                      "can't link audio unit to selected device");
291 
292     p->hw_latency_us = ca_get_hardware_latency(ao);
293 
294     AURenderCallbackStruct render_cb = (AURenderCallbackStruct) {
295         .inputProc       = render_cb_lpcm,
296         .inputProcRefCon = ao,
297     };
298 
299     err = AudioUnitSetProperty(p->audio_unit,
300                                kAudioUnitProperty_SetRenderCallback,
301                                kAudioUnitScope_Input, 0, &render_cb,
302                                sizeof(AURenderCallbackStruct));
303 
304     CHECK_CA_ERROR_L(coreaudio_error_audiounit,
305                      "unable to set render callback on audio unit");
306 
307     return true;
308 
309 coreaudio_error_audiounit:
310     AudioUnitUninitialize(p->audio_unit);
311 coreaudio_error_component:
312     AudioComponentInstanceDispose(p->audio_unit);
313 coreaudio_error:
314     return false;
315 }
316 
stop(struct ao * ao)317 static void stop(struct ao *ao)
318 {
319     struct priv *p = ao->priv;
320     OSStatus err = AudioOutputUnitStop(p->audio_unit);
321     CHECK_CA_WARN("can't stop audio unit");
322 }
323 
start(struct ao * ao)324 static void start(struct ao *ao)
325 {
326     struct priv *p = ao->priv;
327     OSStatus err = AudioOutputUnitStart(p->audio_unit);
328     CHECK_CA_WARN("can't start audio unit");
329 }
330 
331 
uninit(struct ao * ao)332 static void uninit(struct ao *ao)
333 {
334     struct priv *p = ao->priv;
335     AudioOutputUnitStop(p->audio_unit);
336     AudioUnitUninitialize(p->audio_unit);
337     AudioComponentInstanceDispose(p->audio_unit);
338 
339     if (p->original_asbd.mFormatID) {
340         OSStatus err = CA_SET(p->original_asbd_stream,
341                               kAudioStreamPropertyPhysicalFormat,
342                               &p->original_asbd);
343         CHECK_CA_WARN("could not restore physical stream format");
344     }
345 }
346 
hotplug_cb(AudioObjectID id,UInt32 naddr,const AudioObjectPropertyAddress addr[],void * ctx)347 static OSStatus hotplug_cb(AudioObjectID id, UInt32 naddr,
348                            const AudioObjectPropertyAddress addr[],
349                            void *ctx)
350 {
351     struct ao *ao = ctx;
352     MP_VERBOSE(ao, "Handling potential hotplug event...\n");
353     reinit_device(ao);
354     ao_hotplug_event(ao);
355     return noErr;
356 }
357 
358 static uint32_t hotplug_properties[] = {
359     kAudioHardwarePropertyDevices,
360     kAudioHardwarePropertyDefaultOutputDevice
361 };
362 
hotplug_init(struct ao * ao)363 static int hotplug_init(struct ao *ao)
364 {
365     if (!reinit_device(ao))
366         goto coreaudio_error;
367 
368     OSStatus err = noErr;
369     for (int i = 0; i < MP_ARRAY_SIZE(hotplug_properties); i++) {
370         AudioObjectPropertyAddress addr = {
371             hotplug_properties[i],
372             kAudioObjectPropertyScopeGlobal,
373             kAudioObjectPropertyElementMaster
374         };
375         err = AudioObjectAddPropertyListener(
376             kAudioObjectSystemObject, &addr, hotplug_cb, (void *)ao);
377         if (err != noErr) {
378             char *c1 = mp_tag_str(hotplug_properties[i]);
379             char *c2 = mp_tag_str(err);
380             MP_ERR(ao, "failed to set device listener %s (%s)", c1, c2);
381             goto coreaudio_error;
382         }
383     }
384 
385     return 0;
386 
387 coreaudio_error:
388     return -1;
389 }
390 
hotplug_uninit(struct ao * ao)391 static void hotplug_uninit(struct ao *ao)
392 {
393     OSStatus err = noErr;
394     for (int i = 0; i < MP_ARRAY_SIZE(hotplug_properties); i++) {
395         AudioObjectPropertyAddress addr = {
396             hotplug_properties[i],
397             kAudioObjectPropertyScopeGlobal,
398             kAudioObjectPropertyElementMaster
399         };
400         err = AudioObjectRemovePropertyListener(
401             kAudioObjectSystemObject, &addr, hotplug_cb, (void *)ao);
402         if (err != noErr) {
403             char *c1 = mp_tag_str(hotplug_properties[i]);
404             char *c2 = mp_tag_str(err);
405             MP_ERR(ao, "failed to set device listener %s (%s)", c1, c2);
406         }
407     }
408 }
409 
410 #define OPT_BASE_STRUCT struct priv
411 
412 const struct ao_driver audio_out_coreaudio = {
413     .description    = "CoreAudio AudioUnit",
414     .name           = "coreaudio",
415     .uninit         = uninit,
416     .init           = init,
417     .control        = control,
418     .reset          = stop,
419     .start          = start,
420     .hotplug_init   = hotplug_init,
421     .hotplug_uninit = hotplug_uninit,
422     .list_devs      = ca_get_device_list,
423     .priv_size      = sizeof(struct priv),
424     .options = (const struct m_option[]){
425         {"change-physical-format", OPT_FLAG(change_physical_format)},
426         {0}
427     },
428     .options_prefix = "coreaudio",
429 };
430