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