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