]>
Commit | Line | Data |
---|---|---|
1d14ffa9 FB |
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 | ||
6086a565 | 25 | #include "qemu/osdep.h" |
1d14ffa9 | 26 | #include <CoreAudio/CoreAudio.h> |
1d14ffa9 FB |
27 | #include <pthread.h> /* pthread_X */ |
28 | ||
0b8fa32f | 29 | #include "qemu/module.h" |
749bc4bf | 30 | #include "audio.h" |
1d14ffa9 FB |
31 | |
32 | #define AUDIO_CAP "coreaudio" | |
33 | #include "audio_int.h" | |
34 | ||
1d14ffa9 FB |
35 | typedef struct coreaudioVoiceOut { |
36 | HWVoiceOut hw; | |
37 | pthread_mutex_t mutex; | |
38 | AudioDeviceID outputDeviceID; | |
7d6948cd AO |
39 | int frameSizeSetting; |
40 | uint32_t bufferCount; | |
5e941d4b | 41 | UInt32 audioDevicePropertyBufferFrameSize; |
1d14ffa9 | 42 | AudioStreamBasicDescription outputStreamBasicDescription; |
2f79a18f | 43 | AudioDeviceIOProcID ioprocid; |
3ba6e3f6 | 44 | bool enabled; |
1d14ffa9 FB |
45 | } coreaudioVoiceOut; |
46 | ||
3ba6e3f6 AO |
47 | static const AudioObjectPropertyAddress voice_addr = { |
48 | kAudioHardwarePropertyDefaultOutputDevice, | |
49 | kAudioObjectPropertyScopeGlobal, | |
50 | kAudioObjectPropertyElementMaster | |
51 | }; | |
52 | ||
624d1fc3 PM |
53 | static OSStatus coreaudio_get_voice(AudioDeviceID *id) |
54 | { | |
55 | UInt32 size = sizeof(*id); | |
624d1fc3 PM |
56 | |
57 | return AudioObjectGetPropertyData(kAudioObjectSystemObject, | |
3ba6e3f6 | 58 | &voice_addr, |
624d1fc3 PM |
59 | 0, |
60 | NULL, | |
61 | &size, | |
62 | id); | |
63 | } | |
2d99f629 PM |
64 | |
65 | static OSStatus coreaudio_get_framesizerange(AudioDeviceID id, | |
66 | AudioValueRange *framerange) | |
67 | { | |
68 | UInt32 size = sizeof(*framerange); | |
69 | AudioObjectPropertyAddress addr = { | |
70 | kAudioDevicePropertyBufferFrameSizeRange, | |
71 | kAudioDevicePropertyScopeOutput, | |
72 | kAudioObjectPropertyElementMaster | |
73 | }; | |
74 | ||
75 | return AudioObjectGetPropertyData(id, | |
76 | &addr, | |
77 | 0, | |
78 | NULL, | |
79 | &size, | |
80 | framerange); | |
81 | } | |
82 | ||
83 | static OSStatus coreaudio_get_framesize(AudioDeviceID id, UInt32 *framesize) | |
84 | { | |
85 | UInt32 size = sizeof(*framesize); | |
86 | AudioObjectPropertyAddress addr = { | |
87 | kAudioDevicePropertyBufferFrameSize, | |
88 | kAudioDevicePropertyScopeOutput, | |
89 | kAudioObjectPropertyElementMaster | |
90 | }; | |
91 | ||
92 | return AudioObjectGetPropertyData(id, | |
93 | &addr, | |
94 | 0, | |
95 | NULL, | |
96 | &size, | |
97 | framesize); | |
98 | } | |
99 | ||
100 | static OSStatus coreaudio_set_framesize(AudioDeviceID id, UInt32 *framesize) | |
101 | { | |
102 | UInt32 size = sizeof(*framesize); | |
103 | AudioObjectPropertyAddress addr = { | |
104 | kAudioDevicePropertyBufferFrameSize, | |
105 | kAudioDevicePropertyScopeOutput, | |
106 | kAudioObjectPropertyElementMaster | |
107 | }; | |
108 | ||
109 | return AudioObjectSetPropertyData(id, | |
110 | &addr, | |
111 | 0, | |
112 | NULL, | |
113 | size, | |
114 | framesize); | |
115 | } | |
116 | ||
117 | static OSStatus coreaudio_get_streamformat(AudioDeviceID id, | |
118 | AudioStreamBasicDescription *d) | |
119 | { | |
120 | UInt32 size = sizeof(*d); | |
121 | AudioObjectPropertyAddress addr = { | |
122 | kAudioDevicePropertyStreamFormat, | |
123 | kAudioDevicePropertyScopeOutput, | |
124 | kAudioObjectPropertyElementMaster | |
125 | }; | |
126 | ||
127 | return AudioObjectGetPropertyData(id, | |
128 | &addr, | |
129 | 0, | |
130 | NULL, | |
131 | &size, | |
132 | d); | |
133 | } | |
134 | ||
135 | static OSStatus coreaudio_set_streamformat(AudioDeviceID id, | |
136 | AudioStreamBasicDescription *d) | |
137 | { | |
138 | UInt32 size = sizeof(*d); | |
139 | AudioObjectPropertyAddress addr = { | |
140 | kAudioDevicePropertyStreamFormat, | |
141 | kAudioDevicePropertyScopeOutput, | |
142 | kAudioObjectPropertyElementMaster | |
143 | }; | |
144 | ||
145 | return AudioObjectSetPropertyData(id, | |
146 | &addr, | |
147 | 0, | |
148 | NULL, | |
149 | size, | |
150 | d); | |
151 | } | |
152 | ||
153 | static OSStatus coreaudio_get_isrunning(AudioDeviceID id, UInt32 *result) | |
154 | { | |
155 | UInt32 size = sizeof(*result); | |
156 | AudioObjectPropertyAddress addr = { | |
157 | kAudioDevicePropertyDeviceIsRunning, | |
158 | kAudioDevicePropertyScopeOutput, | |
159 | kAudioObjectPropertyElementMaster | |
160 | }; | |
161 | ||
162 | return AudioObjectGetPropertyData(id, | |
163 | &addr, | |
164 | 0, | |
165 | NULL, | |
166 | &size, | |
167 | result); | |
168 | } | |
95a860f6 | 169 | |
1d14ffa9 FB |
170 | static void coreaudio_logstatus (OSStatus status) |
171 | { | |
d9cbb0f3 | 172 | const char *str = "BUG"; |
1d14ffa9 | 173 | |
3c8de96c | 174 | switch (status) { |
1d14ffa9 FB |
175 | case kAudioHardwareNoError: |
176 | str = "kAudioHardwareNoError"; | |
177 | break; | |
178 | ||
179 | case kAudioHardwareNotRunningError: | |
180 | str = "kAudioHardwareNotRunningError"; | |
181 | break; | |
182 | ||
183 | case kAudioHardwareUnspecifiedError: | |
184 | str = "kAudioHardwareUnspecifiedError"; | |
185 | break; | |
186 | ||
187 | case kAudioHardwareUnknownPropertyError: | |
188 | str = "kAudioHardwareUnknownPropertyError"; | |
189 | break; | |
190 | ||
191 | case kAudioHardwareBadPropertySizeError: | |
192 | str = "kAudioHardwareBadPropertySizeError"; | |
193 | break; | |
194 | ||
195 | case kAudioHardwareIllegalOperationError: | |
196 | str = "kAudioHardwareIllegalOperationError"; | |
197 | break; | |
198 | ||
199 | case kAudioHardwareBadDeviceError: | |
200 | str = "kAudioHardwareBadDeviceError"; | |
201 | break; | |
202 | ||
203 | case kAudioHardwareBadStreamError: | |
204 | str = "kAudioHardwareBadStreamError"; | |
205 | break; | |
206 | ||
207 | case kAudioHardwareUnsupportedOperationError: | |
208 | str = "kAudioHardwareUnsupportedOperationError"; | |
209 | break; | |
210 | ||
211 | case kAudioDeviceUnsupportedFormatError: | |
212 | str = "kAudioDeviceUnsupportedFormatError"; | |
213 | break; | |
214 | ||
215 | case kAudioDevicePermissionsError: | |
216 | str = "kAudioDevicePermissionsError"; | |
217 | break; | |
218 | ||
219 | default: | |
744d3644 | 220 | AUD_log (AUDIO_CAP, "Reason: status code %" PRId32 "\n", (int32_t)status); |
1d14ffa9 FB |
221 | return; |
222 | } | |
223 | ||
224 | AUD_log (AUDIO_CAP, "Reason: %s\n", str); | |
225 | } | |
226 | ||
227 | static void GCC_FMT_ATTR (2, 3) coreaudio_logerr ( | |
228 | OSStatus status, | |
229 | const char *fmt, | |
230 | ... | |
231 | ) | |
232 | { | |
233 | va_list ap; | |
234 | ||
235 | va_start (ap, fmt); | |
236 | AUD_log (AUDIO_CAP, fmt, ap); | |
237 | va_end (ap); | |
238 | ||
239 | coreaudio_logstatus (status); | |
240 | } | |
241 | ||
242 | static void GCC_FMT_ATTR (3, 4) coreaudio_logerr2 ( | |
243 | OSStatus status, | |
244 | const char *typ, | |
245 | const char *fmt, | |
246 | ... | |
247 | ) | |
248 | { | |
249 | va_list ap; | |
250 | ||
c0fe3827 | 251 | AUD_log (AUDIO_CAP, "Could not initialize %s\n", typ); |
1d14ffa9 FB |
252 | |
253 | va_start (ap, fmt); | |
254 | AUD_vlog (AUDIO_CAP, fmt, ap); | |
255 | va_end (ap); | |
256 | ||
257 | coreaudio_logstatus (status); | |
258 | } | |
259 | ||
7d6948cd AO |
260 | #define coreaudio_playback_logerr(status, ...) \ |
261 | coreaudio_logerr2(status, "playback", __VA_ARGS__) | |
262 | ||
1d14ffa9 FB |
263 | static int coreaudio_lock (coreaudioVoiceOut *core, const char *fn_name) |
264 | { | |
265 | int err; | |
266 | ||
267 | err = pthread_mutex_lock (&core->mutex); | |
268 | if (err) { | |
c0fe3827 | 269 | dolog ("Could not lock voice for %s\nReason: %s\n", |
1d14ffa9 FB |
270 | fn_name, strerror (err)); |
271 | return -1; | |
272 | } | |
273 | return 0; | |
274 | } | |
275 | ||
276 | static int coreaudio_unlock (coreaudioVoiceOut *core, const char *fn_name) | |
277 | { | |
278 | int err; | |
279 | ||
280 | err = pthread_mutex_unlock (&core->mutex); | |
281 | if (err) { | |
c0fe3827 | 282 | dolog ("Could not unlock voice for %s\nReason: %s\n", |
1d14ffa9 FB |
283 | fn_name, strerror (err)); |
284 | return -1; | |
285 | } | |
286 | return 0; | |
287 | } | |
288 | ||
2ceb8240 KZ |
289 | #define COREAUDIO_WRAPPER_FUNC(name, ret_type, args_decl, args) \ |
290 | static ret_type glue(coreaudio_, name)args_decl \ | |
291 | { \ | |
292 | coreaudioVoiceOut *core = (coreaudioVoiceOut *) hw; \ | |
293 | ret_type ret; \ | |
294 | \ | |
295 | if (coreaudio_lock(core, "coreaudio_" #name)) { \ | |
296 | return 0; \ | |
297 | } \ | |
298 | \ | |
299 | ret = glue(audio_generic_, name)args; \ | |
300 | \ | |
301 | coreaudio_unlock(core, "coreaudio_" #name); \ | |
302 | return ret; \ | |
1d14ffa9 | 303 | } |
2ceb8240 KZ |
304 | COREAUDIO_WRAPPER_FUNC(get_buffer_out, void *, (HWVoiceOut *hw, size_t *size), |
305 | (hw, size)) | |
fdc8c5f4 | 306 | COREAUDIO_WRAPPER_FUNC(put_buffer_out, size_t, |
2ceb8240 KZ |
307 | (HWVoiceOut *hw, void *buf, size_t size), |
308 | (hw, buf, size)) | |
309 | COREAUDIO_WRAPPER_FUNC(write, size_t, (HWVoiceOut *hw, void *buf, size_t size), | |
310 | (hw, buf, size)) | |
311 | #undef COREAUDIO_WRAPPER_FUNC | |
1d14ffa9 FB |
312 | |
313 | /* callback to feed audiooutput buffer */ | |
314 | static OSStatus audioDeviceIOProc( | |
315 | AudioDeviceID inDevice, | |
dcf10e40 ZH |
316 | const AudioTimeStamp *inNow, |
317 | const AudioBufferList *inInputData, | |
318 | const AudioTimeStamp *inInputTime, | |
319 | AudioBufferList *outOutputData, | |
320 | const AudioTimeStamp *inOutputTime, | |
321 | void *hwptr) | |
1d14ffa9 | 322 | { |
2ceb8240 KZ |
323 | UInt32 frameCount, pending_frames; |
324 | void *out = outOutputData->mBuffers[0].mData; | |
1d14ffa9 FB |
325 | HWVoiceOut *hw = hwptr; |
326 | coreaudioVoiceOut *core = (coreaudioVoiceOut *) hwptr; | |
2ceb8240 | 327 | size_t len; |
1d14ffa9 FB |
328 | |
329 | if (coreaudio_lock (core, "audioDeviceIOProc")) { | |
330 | inInputTime = 0; | |
331 | return 0; | |
332 | } | |
333 | ||
3ba6e3f6 AO |
334 | if (inDevice != core->outputDeviceID) { |
335 | coreaudio_unlock (core, "audioDeviceIOProc(old device)"); | |
336 | return 0; | |
337 | } | |
338 | ||
5e941d4b | 339 | frameCount = core->audioDevicePropertyBufferFrameSize; |
2b9cce8c | 340 | pending_frames = hw->pending_emul / hw->info.bytes_per_frame; |
1d14ffa9 FB |
341 | |
342 | /* if there are not enough samples, set signal and return */ | |
2ceb8240 | 343 | if (pending_frames < frameCount) { |
1d14ffa9 FB |
344 | inInputTime = 0; |
345 | coreaudio_unlock (core, "audioDeviceIOProc(empty)"); | |
346 | return 0; | |
347 | } | |
348 | ||
2b9cce8c | 349 | len = frameCount * hw->info.bytes_per_frame; |
2ceb8240 KZ |
350 | while (len) { |
351 | size_t write_len; | |
352 | ssize_t start = ((ssize_t) hw->pos_emul) - hw->pending_emul; | |
353 | if (start < 0) { | |
354 | start += hw->size_emul; | |
355 | } | |
356 | assert(start >= 0 && start < hw->size_emul); | |
1d14ffa9 | 357 | |
2ceb8240 KZ |
358 | write_len = MIN(MIN(hw->pending_emul, len), |
359 | hw->size_emul - start); | |
1d14ffa9 | 360 | |
2ceb8240 KZ |
361 | memcpy(out, hw->buf_emul + start, write_len); |
362 | hw->pending_emul -= write_len; | |
363 | len -= write_len; | |
364 | out += write_len; | |
365 | } | |
1d14ffa9 FB |
366 | |
367 | coreaudio_unlock (core, "audioDeviceIOProc"); | |
368 | return 0; | |
369 | } | |
370 | ||
7d6948cd | 371 | static OSStatus init_out_device(coreaudioVoiceOut *core) |
1d14ffa9 FB |
372 | { |
373 | OSStatus status; | |
5e941d4b | 374 | AudioValueRange frameRange; |
1d14ffa9 | 375 | |
88a0f830 | 376 | status = coreaudio_get_voice(&core->outputDeviceID); |
1d14ffa9 | 377 | if (status != kAudioHardwareNoError) { |
7d6948cd AO |
378 | coreaudio_playback_logerr (status, |
379 | "Could not get default output Device\n"); | |
380 | return status; | |
1d14ffa9 FB |
381 | } |
382 | if (core->outputDeviceID == kAudioDeviceUnknown) { | |
7d6948cd AO |
383 | dolog ("Could not initialize playback - Unknown Audiodevice\n"); |
384 | return status; | |
1d14ffa9 FB |
385 | } |
386 | ||
5e941d4b | 387 | /* get minimum and maximum buffer frame sizes */ |
95a860f6 PM |
388 | status = coreaudio_get_framesizerange(core->outputDeviceID, |
389 | &frameRange); | |
3ba6e3f6 AO |
390 | if (status == kAudioHardwareBadObjectError) { |
391 | return 0; | |
392 | } | |
5e941d4b | 393 | if (status != kAudioHardwareNoError) { |
7d6948cd AO |
394 | coreaudio_playback_logerr (status, |
395 | "Could not get device buffer frame range\n"); | |
396 | return status; | |
5e941d4b FB |
397 | } |
398 | ||
7d6948cd | 399 | if (frameRange.mMinimum > core->frameSizeSetting) { |
5e941d4b FB |
400 | core->audioDevicePropertyBufferFrameSize = (UInt32) frameRange.mMinimum; |
401 | dolog ("warning: Upsizing Buffer Frames to %f\n", frameRange.mMinimum); | |
7d6948cd | 402 | } else if (frameRange.mMaximum < core->frameSizeSetting) { |
5e941d4b FB |
403 | core->audioDevicePropertyBufferFrameSize = (UInt32) frameRange.mMaximum; |
404 | dolog ("warning: Downsizing Buffer Frames to %f\n", frameRange.mMaximum); | |
6c6886bd | 405 | } else { |
7d6948cd | 406 | core->audioDevicePropertyBufferFrameSize = core->frameSizeSetting; |
5e941d4b FB |
407 | } |
408 | ||
409 | /* set Buffer Frame Size */ | |
95a860f6 PM |
410 | status = coreaudio_set_framesize(core->outputDeviceID, |
411 | &core->audioDevicePropertyBufferFrameSize); | |
3ba6e3f6 AO |
412 | if (status == kAudioHardwareBadObjectError) { |
413 | return 0; | |
414 | } | |
1d14ffa9 | 415 | if (status != kAudioHardwareNoError) { |
7d6948cd AO |
416 | coreaudio_playback_logerr (status, |
417 | "Could not set device buffer frame size %" PRIu32 "\n", | |
418 | (uint32_t)core->audioDevicePropertyBufferFrameSize); | |
419 | return status; | |
1d14ffa9 FB |
420 | } |
421 | ||
5e941d4b | 422 | /* get Buffer Frame Size */ |
95a860f6 PM |
423 | status = coreaudio_get_framesize(core->outputDeviceID, |
424 | &core->audioDevicePropertyBufferFrameSize); | |
3ba6e3f6 AO |
425 | if (status == kAudioHardwareBadObjectError) { |
426 | return 0; | |
427 | } | |
1d14ffa9 | 428 | if (status != kAudioHardwareNoError) { |
7d6948cd AO |
429 | coreaudio_playback_logerr (status, |
430 | "Could not get device buffer frame size\n"); | |
431 | return status; | |
1d14ffa9 | 432 | } |
7d6948cd | 433 | core->hw.samples = core->bufferCount * core->audioDevicePropertyBufferFrameSize; |
1d14ffa9 FB |
434 | |
435 | /* get StreamFormat */ | |
95a860f6 PM |
436 | status = coreaudio_get_streamformat(core->outputDeviceID, |
437 | &core->outputStreamBasicDescription); | |
3ba6e3f6 AO |
438 | if (status == kAudioHardwareBadObjectError) { |
439 | return 0; | |
440 | } | |
1d14ffa9 | 441 | if (status != kAudioHardwareNoError) { |
7d6948cd AO |
442 | coreaudio_playback_logerr (status, |
443 | "Could not get Device Stream properties\n"); | |
1d14ffa9 | 444 | core->outputDeviceID = kAudioDeviceUnknown; |
7d6948cd | 445 | return status; |
1d14ffa9 FB |
446 | } |
447 | ||
448 | /* set Samplerate */ | |
95a860f6 PM |
449 | status = coreaudio_set_streamformat(core->outputDeviceID, |
450 | &core->outputStreamBasicDescription); | |
3ba6e3f6 AO |
451 | if (status == kAudioHardwareBadObjectError) { |
452 | return 0; | |
453 | } | |
1d14ffa9 | 454 | if (status != kAudioHardwareNoError) { |
7d6948cd AO |
455 | coreaudio_playback_logerr (status, |
456 | "Could not set samplerate %lf\n", | |
457 | core->outputStreamBasicDescription.mSampleRate); | |
1d14ffa9 | 458 | core->outputDeviceID = kAudioDeviceUnknown; |
7d6948cd | 459 | return status; |
1d14ffa9 FB |
460 | } |
461 | ||
462 | /* set Callback */ | |
2f79a18f PM |
463 | core->ioprocid = NULL; |
464 | status = AudioDeviceCreateIOProcID(core->outputDeviceID, | |
465 | audioDeviceIOProc, | |
7d6948cd | 466 | &core->hw, |
2f79a18f | 467 | &core->ioprocid); |
3ba6e3f6 AO |
468 | if (status == kAudioHardwareBadDeviceError) { |
469 | return 0; | |
470 | } | |
2f79a18f | 471 | if (status != kAudioHardwareNoError || core->ioprocid == NULL) { |
7d6948cd | 472 | coreaudio_playback_logerr (status, "Could not set IOProc\n"); |
1d14ffa9 | 473 | core->outputDeviceID = kAudioDeviceUnknown; |
7d6948cd | 474 | return status; |
1d14ffa9 FB |
475 | } |
476 | ||
1d14ffa9 FB |
477 | return 0; |
478 | } | |
479 | ||
7d6948cd | 480 | static void fini_out_device(coreaudioVoiceOut *core) |
1d14ffa9 FB |
481 | { |
482 | OSStatus status; | |
3ba6e3f6 | 483 | UInt32 isrunning; |
1d14ffa9 | 484 | |
ceb1165e | 485 | /* stop playback */ |
3ba6e3f6 AO |
486 | status = coreaudio_get_isrunning(core->outputDeviceID, &isrunning); |
487 | if (status != kAudioHardwareBadObjectError) { | |
5e941d4b | 488 | if (status != kAudioHardwareNoError) { |
3ba6e3f6 AO |
489 | coreaudio_logerr(status, |
490 | "Could not determine whether Device is playing\n"); | |
491 | } | |
492 | ||
493 | if (isrunning) { | |
494 | status = AudioDeviceStop(core->outputDeviceID, core->ioprocid); | |
495 | if (status != kAudioHardwareBadDeviceError && status != kAudioHardwareNoError) { | |
496 | coreaudio_logerr(status, "Could not stop playback\n"); | |
497 | } | |
5e941d4b | 498 | } |
1d14ffa9 | 499 | } |
ceb1165e VR |
500 | |
501 | /* remove callback */ | |
502 | status = AudioDeviceDestroyIOProcID(core->outputDeviceID, | |
503 | core->ioprocid); | |
3ba6e3f6 | 504 | if (status != kAudioHardwareBadDeviceError && status != kAudioHardwareNoError) { |
ceb1165e VR |
505 | coreaudio_logerr(status, "Could not remove IOProc\n"); |
506 | } | |
1d14ffa9 | 507 | core->outputDeviceID = kAudioDeviceUnknown; |
7d6948cd AO |
508 | } |
509 | ||
3ba6e3f6 AO |
510 | static void update_device_playback_state(coreaudioVoiceOut *core) |
511 | { | |
512 | OSStatus status; | |
513 | UInt32 isrunning; | |
514 | ||
515 | status = coreaudio_get_isrunning(core->outputDeviceID, &isrunning); | |
516 | if (status != kAudioHardwareNoError) { | |
517 | if (status != kAudioHardwareBadObjectError) { | |
518 | coreaudio_logerr(status, | |
519 | "Could not determine whether Device is playing\n"); | |
520 | } | |
521 | ||
522 | return; | |
523 | } | |
524 | ||
525 | if (core->enabled) { | |
526 | /* start playback */ | |
527 | if (!isrunning) { | |
528 | status = AudioDeviceStart(core->outputDeviceID, core->ioprocid); | |
529 | if (status != kAudioHardwareBadDeviceError && status != kAudioHardwareNoError) { | |
530 | coreaudio_logerr (status, "Could not resume playback\n"); | |
531 | } | |
532 | } | |
533 | } else { | |
534 | /* stop playback */ | |
535 | if (isrunning) { | |
536 | status = AudioDeviceStop(core->outputDeviceID, | |
537 | core->ioprocid); | |
538 | if (status != kAudioHardwareBadDeviceError && status != kAudioHardwareNoError) { | |
539 | coreaudio_logerr(status, "Could not pause playback\n"); | |
540 | } | |
541 | } | |
542 | } | |
543 | } | |
544 | ||
545 | static OSStatus handle_voice_change( | |
546 | AudioObjectID in_object_id, | |
547 | UInt32 in_number_addresses, | |
548 | const AudioObjectPropertyAddress *in_addresses, | |
549 | void *in_client_data) | |
550 | { | |
551 | OSStatus status; | |
552 | coreaudioVoiceOut *core = in_client_data; | |
553 | ||
554 | if (coreaudio_lock(core, __func__)) { | |
555 | abort(); | |
556 | } | |
557 | ||
558 | if (core->outputDeviceID) { | |
559 | fini_out_device(core); | |
560 | } | |
561 | ||
562 | status = init_out_device(core); | |
563 | if (!status) { | |
564 | update_device_playback_state(core); | |
565 | } | |
566 | ||
567 | coreaudio_unlock (core, __func__); | |
568 | return status; | |
569 | } | |
570 | ||
7d6948cd AO |
571 | static int coreaudio_init_out(HWVoiceOut *hw, struct audsettings *as, |
572 | void *drv_opaque) | |
573 | { | |
574 | OSStatus status; | |
575 | coreaudioVoiceOut *core = (coreaudioVoiceOut *) hw; | |
576 | int err; | |
577 | Audiodev *dev = drv_opaque; | |
578 | AudiodevCoreaudioPerDirectionOptions *cpdo = dev->u.coreaudio.out; | |
579 | struct audsettings obt_as; | |
580 | ||
581 | /* create mutex */ | |
582 | err = pthread_mutex_init(&core->mutex, NULL); | |
583 | if (err) { | |
584 | dolog("Could not create mutex\nReason: %s\n", strerror (err)); | |
3ba6e3f6 AO |
585 | goto mutex_error; |
586 | } | |
587 | ||
588 | if (coreaudio_lock(core, __func__)) { | |
589 | goto lock_error; | |
7d6948cd AO |
590 | } |
591 | ||
592 | obt_as = *as; | |
593 | as = &obt_as; | |
594 | as->fmt = AUDIO_FORMAT_F32; | |
595 | audio_pcm_init_info (&hw->info, as); | |
596 | ||
597 | core->frameSizeSetting = audio_buffer_frames( | |
598 | qapi_AudiodevCoreaudioPerDirectionOptions_base(cpdo), as, 11610); | |
599 | ||
600 | core->bufferCount = cpdo->has_buffer_count ? cpdo->buffer_count : 4; | |
601 | core->outputStreamBasicDescription.mSampleRate = (Float64) as->freq; | |
602 | ||
3ba6e3f6 AO |
603 | status = AudioObjectAddPropertyListener(kAudioObjectSystemObject, |
604 | &voice_addr, handle_voice_change, | |
605 | core); | |
606 | if (status != kAudioHardwareNoError) { | |
607 | coreaudio_playback_logerr (status, | |
608 | "Could not listen to voice property change\n"); | |
609 | goto listener_error; | |
610 | } | |
611 | ||
7d6948cd | 612 | if (init_out_device(core)) { |
3ba6e3f6 | 613 | goto device_error; |
7d6948cd AO |
614 | } |
615 | ||
3ba6e3f6 | 616 | coreaudio_unlock(core, __func__); |
7d6948cd | 617 | return 0; |
3ba6e3f6 AO |
618 | |
619 | device_error: | |
620 | status = AudioObjectRemovePropertyListener(kAudioObjectSystemObject, | |
621 | &voice_addr, | |
622 | handle_voice_change, | |
623 | core); | |
624 | if (status != kAudioHardwareNoError) { | |
625 | coreaudio_playback_logerr(status, | |
626 | "Could not remove voice property change listener\n"); | |
627 | } | |
628 | ||
629 | listener_error: | |
630 | coreaudio_unlock(core, __func__); | |
631 | ||
632 | lock_error: | |
633 | err = pthread_mutex_destroy(&core->mutex); | |
634 | if (err) { | |
635 | dolog("Could not destroy mutex\nReason: %s\n", strerror (err)); | |
636 | } | |
637 | ||
638 | mutex_error: | |
639 | return -1; | |
7d6948cd AO |
640 | } |
641 | ||
642 | static void coreaudio_fini_out (HWVoiceOut *hw) | |
643 | { | |
644 | OSStatus status; | |
645 | int err; | |
646 | coreaudioVoiceOut *core = (coreaudioVoiceOut *) hw; | |
647 | ||
3ba6e3f6 AO |
648 | if (coreaudio_lock(core, __func__)) { |
649 | abort(); | |
650 | } | |
651 | ||
652 | status = AudioObjectRemovePropertyListener(kAudioObjectSystemObject, | |
653 | &voice_addr, | |
654 | handle_voice_change, | |
655 | core); | |
656 | if (status != kAudioHardwareNoError) { | |
657 | coreaudio_logerr(status, "Could not remove voice property change listener\n"); | |
658 | } | |
659 | ||
7d6948cd | 660 | fini_out_device(core); |
1d14ffa9 | 661 | |
3ba6e3f6 AO |
662 | coreaudio_unlock(core, __func__); |
663 | ||
1d14ffa9 FB |
664 | /* destroy mutex */ |
665 | err = pthread_mutex_destroy(&core->mutex); | |
666 | if (err) { | |
c0fe3827 | 667 | dolog("Could not destroy mutex\nReason: %s\n", strerror (err)); |
1d14ffa9 FB |
668 | } |
669 | } | |
670 | ||
571a8c52 | 671 | static void coreaudio_enable_out(HWVoiceOut *hw, bool enable) |
1d14ffa9 | 672 | { |
1d14ffa9 FB |
673 | coreaudioVoiceOut *core = (coreaudioVoiceOut *) hw; |
674 | ||
3ba6e3f6 AO |
675 | if (coreaudio_lock(core, __func__)) { |
676 | abort(); | |
1d14ffa9 | 677 | } |
3ba6e3f6 AO |
678 | |
679 | core->enabled = enable; | |
680 | update_device_playback_state(core); | |
681 | ||
682 | coreaudio_unlock(core, __func__); | |
1d14ffa9 FB |
683 | } |
684 | ||
71830221 | 685 | static void *coreaudio_audio_init(Audiodev *dev) |
1d14ffa9 | 686 | { |
17c56dc1 | 687 | return dev; |
1d14ffa9 FB |
688 | } |
689 | ||
690 | static void coreaudio_audio_fini (void *opaque) | |
691 | { | |
1d14ffa9 FB |
692 | } |
693 | ||
35f4b58c | 694 | static struct audio_pcm_ops coreaudio_pcm_ops = { |
1dd3e4d1 JQ |
695 | .init_out = coreaudio_init_out, |
696 | .fini_out = coreaudio_fini_out, | |
fdc8c5f4 | 697 | /* wrapper for audio_generic_write */ |
2ceb8240 | 698 | .write = coreaudio_write, |
fdc8c5f4 | 699 | /* wrapper for audio_generic_get_buffer_out */ |
2ceb8240 | 700 | .get_buffer_out = coreaudio_get_buffer_out, |
fdc8c5f4 VR |
701 | /* wrapper for audio_generic_put_buffer_out */ |
702 | .put_buffer_out = coreaudio_put_buffer_out, | |
571a8c52 | 703 | .enable_out = coreaudio_enable_out |
1d14ffa9 FB |
704 | }; |
705 | ||
d3893a39 | 706 | static struct audio_driver coreaudio_audio_driver = { |
bee37f32 JQ |
707 | .name = "coreaudio", |
708 | .descr = "CoreAudio http://developer.apple.com/audio/coreaudio.html", | |
bee37f32 JQ |
709 | .init = coreaudio_audio_init, |
710 | .fini = coreaudio_audio_fini, | |
711 | .pcm_ops = &coreaudio_pcm_ops, | |
712 | .can_be_default = 1, | |
713 | .max_voices_out = 1, | |
714 | .max_voices_in = 0, | |
715 | .voice_size_out = sizeof (coreaudioVoiceOut), | |
716 | .voice_size_in = 0 | |
1d14ffa9 | 717 | }; |
d3893a39 GH |
718 | |
719 | static void register_audio_coreaudio(void) | |
720 | { | |
721 | audio_driver_register(&coreaudio_audio_driver); | |
722 | } | |
723 | type_init(register_audio_coreaudio); |