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