xref: /illumos-kvm-cmd/audio/coreaudio.c (revision 68396ea9)
1 /*
2  * QEMU OS X CoreAudio audio driver
3  *
4  * Copyright (c) 2005 Mike Kronenberg
5  *
6  * Permission is hereby granted, free of charge, to any person obtaining a copy
7  * of this software and associated documentation files (the "Software"), to deal
8  * in the Software without restriction, including without limitation the rights
9  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10  * copies of the Software, and to permit persons to whom the Software is
11  * furnished to do so, subject to the following conditions:
12  *
13  * The above copyright notice and this permission notice shall be included in
14  * all copies or substantial portions of the Software.
15  *
16  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
19  * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22  * THE SOFTWARE.
23  */
24 
25 #include <CoreAudio/CoreAudio.h>
26 #include <string.h>             /* strerror */
27 #include <pthread.h>            /* pthread_X */
28 
29 #include "qemu-common.h"
30 #include "audio.h"
31 
32 #define AUDIO_CAP "coreaudio"
33 #include "audio_int.h"
34 
35 struct {
36     int buffer_frames;
37     int nbuffers;
38     int isAtexit;
39 } conf = {
40     .buffer_frames = 512,
41     .nbuffers = 4,
42     .isAtexit = 0
43 };
44 
45 typedef struct coreaudioVoiceOut {
46     HWVoiceOut hw;
47     pthread_mutex_t mutex;
48     int isAtexit;
49     AudioDeviceID outputDeviceID;
50     UInt32 audioDevicePropertyBufferFrameSize;
51     AudioStreamBasicDescription outputStreamBasicDescription;
52     int live;
53     int decr;
54     int rpos;
55 } coreaudioVoiceOut;
56 
coreaudio_logstatus(OSStatus status)57 static void coreaudio_logstatus (OSStatus status)
58 {
59     char *str = "BUG";
60 
61     switch(status) {
62     case kAudioHardwareNoError:
63         str = "kAudioHardwareNoError";
64         break;
65 
66     case kAudioHardwareNotRunningError:
67         str = "kAudioHardwareNotRunningError";
68         break;
69 
70     case kAudioHardwareUnspecifiedError:
71         str = "kAudioHardwareUnspecifiedError";
72         break;
73 
74     case kAudioHardwareUnknownPropertyError:
75         str = "kAudioHardwareUnknownPropertyError";
76         break;
77 
78     case kAudioHardwareBadPropertySizeError:
79         str = "kAudioHardwareBadPropertySizeError";
80         break;
81 
82     case kAudioHardwareIllegalOperationError:
83         str = "kAudioHardwareIllegalOperationError";
84         break;
85 
86     case kAudioHardwareBadDeviceError:
87         str = "kAudioHardwareBadDeviceError";
88         break;
89 
90     case kAudioHardwareBadStreamError:
91         str = "kAudioHardwareBadStreamError";
92         break;
93 
94     case kAudioHardwareUnsupportedOperationError:
95         str = "kAudioHardwareUnsupportedOperationError";
96         break;
97 
98     case kAudioDeviceUnsupportedFormatError:
99         str = "kAudioDeviceUnsupportedFormatError";
100         break;
101 
102     case kAudioDevicePermissionsError:
103         str = "kAudioDevicePermissionsError";
104         break;
105 
106     default:
107         AUD_log (AUDIO_CAP, "Reason: status code %ld\n", status);
108         return;
109     }
110 
111     AUD_log (AUDIO_CAP, "Reason: %s\n", str);
112 }
113 
coreaudio_logerr(OSStatus status,const char * fmt,...)114 static void GCC_FMT_ATTR (2, 3) coreaudio_logerr (
115     OSStatus status,
116     const char *fmt,
117     ...
118     )
119 {
120     va_list ap;
121 
122     va_start (ap, fmt);
123     AUD_log (AUDIO_CAP, fmt, ap);
124     va_end (ap);
125 
126     coreaudio_logstatus (status);
127 }
128 
coreaudio_logerr2(OSStatus status,const char * typ,const char * fmt,...)129 static void GCC_FMT_ATTR (3, 4) coreaudio_logerr2 (
130     OSStatus status,
131     const char *typ,
132     const char *fmt,
133     ...
134     )
135 {
136     va_list ap;
137 
138     AUD_log (AUDIO_CAP, "Could not initialize %s\n", typ);
139 
140     va_start (ap, fmt);
141     AUD_vlog (AUDIO_CAP, fmt, ap);
142     va_end (ap);
143 
144     coreaudio_logstatus (status);
145 }
146 
isPlaying(AudioDeviceID outputDeviceID)147 static inline UInt32 isPlaying (AudioDeviceID outputDeviceID)
148 {
149     OSStatus status;
150     UInt32 result = 0;
151     UInt32 propertySize = sizeof(outputDeviceID);
152     status = AudioDeviceGetProperty(
153         outputDeviceID, 0, 0,
154         kAudioDevicePropertyDeviceIsRunning, &propertySize, &result);
155     if (status != kAudioHardwareNoError) {
156         coreaudio_logerr(status,
157                          "Could not determine whether Device is playing\n");
158     }
159     return result;
160 }
161 
coreaudio_atexit(void)162 static void coreaudio_atexit (void)
163 {
164     conf.isAtexit = 1;
165 }
166 
coreaudio_lock(coreaudioVoiceOut * core,const char * fn_name)167 static int coreaudio_lock (coreaudioVoiceOut *core, const char *fn_name)
168 {
169     int err;
170 
171     err = pthread_mutex_lock (&core->mutex);
172     if (err) {
173         dolog ("Could not lock voice for %s\nReason: %s\n",
174                fn_name, strerror (err));
175         return -1;
176     }
177     return 0;
178 }
179 
coreaudio_unlock(coreaudioVoiceOut * core,const char * fn_name)180 static int coreaudio_unlock (coreaudioVoiceOut *core, const char *fn_name)
181 {
182     int err;
183 
184     err = pthread_mutex_unlock (&core->mutex);
185     if (err) {
186         dolog ("Could not unlock voice for %s\nReason: %s\n",
187                fn_name, strerror (err));
188         return -1;
189     }
190     return 0;
191 }
192 
coreaudio_run_out(HWVoiceOut * hw,int live)193 static int coreaudio_run_out (HWVoiceOut *hw, int live)
194 {
195     int decr;
196     coreaudioVoiceOut *core = (coreaudioVoiceOut *) hw;
197 
198     if (coreaudio_lock (core, "coreaudio_run_out")) {
199         return 0;
200     }
201 
202     if (core->decr > live) {
203         ldebug ("core->decr %d live %d core->live %d\n",
204                 core->decr,
205                 live,
206                 core->live);
207     }
208 
209     decr = audio_MIN (core->decr, live);
210     core->decr -= decr;
211 
212     core->live = live - decr;
213     hw->rpos = core->rpos;
214 
215     coreaudio_unlock (core, "coreaudio_run_out");
216     return decr;
217 }
218 
219 /* callback to feed audiooutput buffer */
audioDeviceIOProc(AudioDeviceID inDevice,const AudioTimeStamp * inNow,const AudioBufferList * inInputData,const AudioTimeStamp * inInputTime,AudioBufferList * outOutputData,const AudioTimeStamp * inOutputTime,void * hwptr)220 static OSStatus audioDeviceIOProc(
221     AudioDeviceID inDevice,
222     const AudioTimeStamp* inNow,
223     const AudioBufferList* inInputData,
224     const AudioTimeStamp* inInputTime,
225     AudioBufferList* outOutputData,
226     const AudioTimeStamp* inOutputTime,
227     void* hwptr)
228 {
229     UInt32 frame, frameCount;
230     float *out = outOutputData->mBuffers[0].mData;
231     HWVoiceOut *hw = hwptr;
232     coreaudioVoiceOut *core = (coreaudioVoiceOut *) hwptr;
233     int rpos, live;
234     struct st_sample *src;
235 #ifndef FLOAT_MIXENG
236 #ifdef RECIPROCAL
237     const float scale = 1.f / UINT_MAX;
238 #else
239     const float scale = UINT_MAX;
240 #endif
241 #endif
242 
243     if (coreaudio_lock (core, "audioDeviceIOProc")) {
244         inInputTime = 0;
245         return 0;
246     }
247 
248     frameCount = core->audioDevicePropertyBufferFrameSize;
249     live = core->live;
250 
251     /* if there are not enough samples, set signal and return */
252     if (live < frameCount) {
253         inInputTime = 0;
254         coreaudio_unlock (core, "audioDeviceIOProc(empty)");
255         return 0;
256     }
257 
258     rpos = core->rpos;
259     src = hw->mix_buf + rpos;
260 
261     /* fill buffer */
262     for (frame = 0; frame < frameCount; frame++) {
263 #ifdef FLOAT_MIXENG
264         *out++ = src[frame].l; /* left channel */
265         *out++ = src[frame].r; /* right channel */
266 #else
267 #ifdef RECIPROCAL
268         *out++ = src[frame].l * scale; /* left channel */
269         *out++ = src[frame].r * scale; /* right channel */
270 #else
271         *out++ = src[frame].l / scale; /* left channel */
272         *out++ = src[frame].r / scale; /* right channel */
273 #endif
274 #endif
275     }
276 
277     rpos = (rpos + frameCount) % hw->samples;
278     core->decr += frameCount;
279     core->rpos = rpos;
280 
281     coreaudio_unlock (core, "audioDeviceIOProc");
282     return 0;
283 }
284 
coreaudio_write(SWVoiceOut * sw,void * buf,int len)285 static int coreaudio_write (SWVoiceOut *sw, void *buf, int len)
286 {
287     return audio_pcm_sw_write (sw, buf, len);
288 }
289 
coreaudio_init_out(HWVoiceOut * hw,struct audsettings * as)290 static int coreaudio_init_out (HWVoiceOut *hw, struct audsettings *as)
291 {
292     OSStatus status;
293     coreaudioVoiceOut *core = (coreaudioVoiceOut *) hw;
294     UInt32 propertySize;
295     int err;
296     const char *typ = "playback";
297     AudioValueRange frameRange;
298 
299     /* create mutex */
300     err = pthread_mutex_init(&core->mutex, NULL);
301     if (err) {
302         dolog("Could not create mutex\nReason: %s\n", strerror (err));
303         return -1;
304     }
305 
306     audio_pcm_init_info (&hw->info, as);
307 
308     /* open default output device */
309     propertySize = sizeof(core->outputDeviceID);
310     status = AudioHardwareGetProperty(
311         kAudioHardwarePropertyDefaultOutputDevice,
312         &propertySize,
313         &core->outputDeviceID);
314     if (status != kAudioHardwareNoError) {
315         coreaudio_logerr2 (status, typ,
316                            "Could not get default output Device\n");
317         return -1;
318     }
319     if (core->outputDeviceID == kAudioDeviceUnknown) {
320         dolog ("Could not initialize %s - Unknown Audiodevice\n", typ);
321         return -1;
322     }
323 
324     /* get minimum and maximum buffer frame sizes */
325     propertySize = sizeof(frameRange);
326     status = AudioDeviceGetProperty(
327         core->outputDeviceID,
328         0,
329         0,
330         kAudioDevicePropertyBufferFrameSizeRange,
331         &propertySize,
332         &frameRange);
333     if (status != kAudioHardwareNoError) {
334         coreaudio_logerr2 (status, typ,
335                            "Could not get device buffer frame range\n");
336         return -1;
337     }
338 
339     if (frameRange.mMinimum > conf.buffer_frames) {
340         core->audioDevicePropertyBufferFrameSize = (UInt32) frameRange.mMinimum;
341         dolog ("warning: Upsizing Buffer Frames to %f\n", frameRange.mMinimum);
342     }
343     else if (frameRange.mMaximum < conf.buffer_frames) {
344         core->audioDevicePropertyBufferFrameSize = (UInt32) frameRange.mMaximum;
345         dolog ("warning: Downsizing Buffer Frames to %f\n", frameRange.mMaximum);
346     }
347     else {
348         core->audioDevicePropertyBufferFrameSize = conf.buffer_frames;
349     }
350 
351     /* set Buffer Frame Size */
352     propertySize = sizeof(core->audioDevicePropertyBufferFrameSize);
353     status = AudioDeviceSetProperty(
354         core->outputDeviceID,
355         NULL,
356         0,
357         false,
358         kAudioDevicePropertyBufferFrameSize,
359         propertySize,
360         &core->audioDevicePropertyBufferFrameSize);
361     if (status != kAudioHardwareNoError) {
362         coreaudio_logerr2 (status, typ,
363                            "Could not set device buffer frame size %ld\n",
364                            core->audioDevicePropertyBufferFrameSize);
365         return -1;
366     }
367 
368     /* get Buffer Frame Size */
369     propertySize = sizeof(core->audioDevicePropertyBufferFrameSize);
370     status = AudioDeviceGetProperty(
371         core->outputDeviceID,
372         0,
373         false,
374         kAudioDevicePropertyBufferFrameSize,
375         &propertySize,
376         &core->audioDevicePropertyBufferFrameSize);
377     if (status != kAudioHardwareNoError) {
378         coreaudio_logerr2 (status, typ,
379                            "Could not get device buffer frame size\n");
380         return -1;
381     }
382     hw->samples = conf.nbuffers * core->audioDevicePropertyBufferFrameSize;
383 
384     /* get StreamFormat */
385     propertySize = sizeof(core->outputStreamBasicDescription);
386     status = AudioDeviceGetProperty(
387         core->outputDeviceID,
388         0,
389         false,
390         kAudioDevicePropertyStreamFormat,
391         &propertySize,
392         &core->outputStreamBasicDescription);
393     if (status != kAudioHardwareNoError) {
394         coreaudio_logerr2 (status, typ,
395                            "Could not get Device Stream properties\n");
396         core->outputDeviceID = kAudioDeviceUnknown;
397         return -1;
398     }
399 
400     /* set Samplerate */
401     core->outputStreamBasicDescription.mSampleRate = (Float64) as->freq;
402     propertySize = sizeof(core->outputStreamBasicDescription);
403     status = AudioDeviceSetProperty(
404         core->outputDeviceID,
405         0,
406         0,
407         0,
408         kAudioDevicePropertyStreamFormat,
409         propertySize,
410         &core->outputStreamBasicDescription);
411     if (status != kAudioHardwareNoError) {
412         coreaudio_logerr2 (status, typ, "Could not set samplerate %d\n",
413                            as->freq);
414         core->outputDeviceID = kAudioDeviceUnknown;
415         return -1;
416     }
417 
418     /* set Callback */
419     status = AudioDeviceAddIOProc(core->outputDeviceID, audioDeviceIOProc, hw);
420     if (status != kAudioHardwareNoError) {
421         coreaudio_logerr2 (status, typ, "Could not set IOProc\n");
422         core->outputDeviceID = kAudioDeviceUnknown;
423         return -1;
424     }
425 
426     /* start Playback */
427     if (!isPlaying(core->outputDeviceID)) {
428         status = AudioDeviceStart(core->outputDeviceID, audioDeviceIOProc);
429         if (status != kAudioHardwareNoError) {
430             coreaudio_logerr2 (status, typ, "Could not start playback\n");
431             AudioDeviceRemoveIOProc(core->outputDeviceID, audioDeviceIOProc);
432             core->outputDeviceID = kAudioDeviceUnknown;
433             return -1;
434         }
435     }
436 
437     return 0;
438 }
439 
coreaudio_fini_out(HWVoiceOut * hw)440 static void coreaudio_fini_out (HWVoiceOut *hw)
441 {
442     OSStatus status;
443     int err;
444     coreaudioVoiceOut *core = (coreaudioVoiceOut *) hw;
445 
446     if (!conf.isAtexit) {
447         /* stop playback */
448         if (isPlaying(core->outputDeviceID)) {
449             status = AudioDeviceStop(core->outputDeviceID, audioDeviceIOProc);
450             if (status != kAudioHardwareNoError) {
451                 coreaudio_logerr (status, "Could not stop playback\n");
452             }
453         }
454 
455         /* remove callback */
456         status = AudioDeviceRemoveIOProc(core->outputDeviceID,
457                                          audioDeviceIOProc);
458         if (status != kAudioHardwareNoError) {
459             coreaudio_logerr (status, "Could not remove IOProc\n");
460         }
461     }
462     core->outputDeviceID = kAudioDeviceUnknown;
463 
464     /* destroy mutex */
465     err = pthread_mutex_destroy(&core->mutex);
466     if (err) {
467         dolog("Could not destroy mutex\nReason: %s\n", strerror (err));
468     }
469 }
470 
coreaudio_ctl_out(HWVoiceOut * hw,int cmd,...)471 static int coreaudio_ctl_out (HWVoiceOut *hw, int cmd, ...)
472 {
473     OSStatus status;
474     coreaudioVoiceOut *core = (coreaudioVoiceOut *) hw;
475 
476     switch (cmd) {
477     case VOICE_ENABLE:
478         /* start playback */
479         if (!isPlaying(core->outputDeviceID)) {
480             status = AudioDeviceStart(core->outputDeviceID, audioDeviceIOProc);
481             if (status != kAudioHardwareNoError) {
482                 coreaudio_logerr (status, "Could not resume playback\n");
483             }
484         }
485         break;
486 
487     case VOICE_DISABLE:
488         /* stop playback */
489         if (!conf.isAtexit) {
490             if (isPlaying(core->outputDeviceID)) {
491                 status = AudioDeviceStop(core->outputDeviceID, audioDeviceIOProc);
492                 if (status != kAudioHardwareNoError) {
493                     coreaudio_logerr (status, "Could not pause playback\n");
494                 }
495             }
496         }
497         break;
498     }
499     return 0;
500 }
501 
coreaudio_audio_init(void)502 static void *coreaudio_audio_init (void)
503 {
504     atexit(coreaudio_atexit);
505     return &coreaudio_audio_init;
506 }
507 
coreaudio_audio_fini(void * opaque)508 static void coreaudio_audio_fini (void *opaque)
509 {
510     (void) opaque;
511 }
512 
513 static struct audio_option coreaudio_options[] = {
514     {
515         .name  = "BUFFER_SIZE",
516         .tag   = AUD_OPT_INT,
517         .valp  = &conf.buffer_frames,
518         .descr = "Size of the buffer in frames"
519     },
520     {
521         .name  = "BUFFER_COUNT",
522         .tag   = AUD_OPT_INT,
523         .valp  = &conf.nbuffers,
524         .descr = "Number of buffers"
525     },
526     { /* End of list */ }
527 };
528 
529 static struct audio_pcm_ops coreaudio_pcm_ops = {
530     .init_out = coreaudio_init_out,
531     .fini_out = coreaudio_fini_out,
532     .run_out  = coreaudio_run_out,
533     .write    = coreaudio_write,
534     .ctl_out  = coreaudio_ctl_out
535 };
536 
537 struct audio_driver coreaudio_audio_driver = {
538     .name           = "coreaudio",
539     .descr          = "CoreAudio http://developer.apple.com/audio/coreaudio.html",
540     .options        = coreaudio_options,
541     .init           = coreaudio_audio_init,
542     .fini           = coreaudio_audio_fini,
543     .pcm_ops        = &coreaudio_pcm_ops,
544     .can_be_default = 1,
545     .max_voices_out = 1,
546     .max_voices_in  = 0,
547     .voice_size_out = sizeof (coreaudioVoiceOut),
548     .voice_size_in  = 0
549 };
550