]>
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 KZ |
335 | while (len) { |
336 | size_t write_len; | |
337 | ssize_t start = ((ssize_t) hw->pos_emul) - hw->pending_emul; | |
338 | if (start < 0) { | |
339 | start += hw->size_emul; | |
340 | } | |
341 | assert(start >= 0 && start < hw->size_emul); | |
1d14ffa9 | 342 | |
2ceb8240 KZ |
343 | write_len = MIN(MIN(hw->pending_emul, len), |
344 | hw->size_emul - start); | |
1d14ffa9 | 345 | |
2ceb8240 KZ |
346 | memcpy(out, hw->buf_emul + start, write_len); |
347 | hw->pending_emul -= write_len; | |
348 | len -= write_len; | |
349 | out += write_len; | |
350 | } | |
1d14ffa9 | 351 | |
eb1a35e4 | 352 | coreaudio_buf_unlock (core, "audioDeviceIOProc"); |
1d14ffa9 FB |
353 | return 0; |
354 | } | |
355 | ||
7d6948cd | 356 | static OSStatus init_out_device(coreaudioVoiceOut *core) |
1d14ffa9 FB |
357 | { |
358 | OSStatus status; | |
5e941d4b | 359 | AudioValueRange frameRange; |
1d14ffa9 | 360 | |
986bdbc6 AO |
361 | AudioStreamBasicDescription streamBasicDescription = { |
362 | .mBitsPerChannel = core->hw.info.bits, | |
363 | .mBytesPerFrame = core->hw.info.bytes_per_frame, | |
364 | .mBytesPerPacket = core->hw.info.bytes_per_frame, | |
365 | .mChannelsPerFrame = core->hw.info.nchannels, | |
366 | .mFormatFlags = kLinearPCMFormatFlagIsFloat, | |
367 | .mFormatID = kAudioFormatLinearPCM, | |
368 | .mFramesPerPacket = 1, | |
369 | .mSampleRate = core->hw.info.freq | |
370 | }; | |
371 | ||
88a0f830 | 372 | status = coreaudio_get_voice(&core->outputDeviceID); |
1d14ffa9 | 373 | if (status != kAudioHardwareNoError) { |
7d6948cd AO |
374 | coreaudio_playback_logerr (status, |
375 | "Could not get default output Device\n"); | |
376 | return status; | |
1d14ffa9 FB |
377 | } |
378 | if (core->outputDeviceID == kAudioDeviceUnknown) { | |
7d6948cd AO |
379 | dolog ("Could not initialize playback - Unknown Audiodevice\n"); |
380 | return status; | |
1d14ffa9 FB |
381 | } |
382 | ||
5e941d4b | 383 | /* get minimum and maximum buffer frame sizes */ |
95a860f6 PM |
384 | status = coreaudio_get_framesizerange(core->outputDeviceID, |
385 | &frameRange); | |
3ba6e3f6 AO |
386 | if (status == kAudioHardwareBadObjectError) { |
387 | return 0; | |
388 | } | |
5e941d4b | 389 | if (status != kAudioHardwareNoError) { |
7d6948cd AO |
390 | coreaudio_playback_logerr (status, |
391 | "Could not get device buffer frame range\n"); | |
392 | return status; | |
5e941d4b FB |
393 | } |
394 | ||
7d6948cd | 395 | if (frameRange.mMinimum > core->frameSizeSetting) { |
5e941d4b FB |
396 | core->audioDevicePropertyBufferFrameSize = (UInt32) frameRange.mMinimum; |
397 | dolog ("warning: Upsizing Buffer Frames to %f\n", frameRange.mMinimum); | |
7d6948cd | 398 | } else if (frameRange.mMaximum < core->frameSizeSetting) { |
5e941d4b FB |
399 | core->audioDevicePropertyBufferFrameSize = (UInt32) frameRange.mMaximum; |
400 | dolog ("warning: Downsizing Buffer Frames to %f\n", frameRange.mMaximum); | |
6c6886bd | 401 | } else { |
7d6948cd | 402 | core->audioDevicePropertyBufferFrameSize = core->frameSizeSetting; |
5e941d4b FB |
403 | } |
404 | ||
405 | /* set Buffer Frame Size */ | |
95a860f6 PM |
406 | status = coreaudio_set_framesize(core->outputDeviceID, |
407 | &core->audioDevicePropertyBufferFrameSize); | |
3ba6e3f6 AO |
408 | if (status == kAudioHardwareBadObjectError) { |
409 | return 0; | |
410 | } | |
1d14ffa9 | 411 | if (status != kAudioHardwareNoError) { |
7d6948cd AO |
412 | coreaudio_playback_logerr (status, |
413 | "Could not set device buffer frame size %" PRIu32 "\n", | |
414 | (uint32_t)core->audioDevicePropertyBufferFrameSize); | |
415 | return status; | |
1d14ffa9 FB |
416 | } |
417 | ||
5e941d4b | 418 | /* get Buffer Frame Size */ |
95a860f6 PM |
419 | status = coreaudio_get_framesize(core->outputDeviceID, |
420 | &core->audioDevicePropertyBufferFrameSize); | |
3ba6e3f6 AO |
421 | if (status == kAudioHardwareBadObjectError) { |
422 | return 0; | |
423 | } | |
1d14ffa9 | 424 | if (status != kAudioHardwareNoError) { |
7d6948cd AO |
425 | coreaudio_playback_logerr (status, |
426 | "Could not get device buffer frame size\n"); | |
427 | return status; | |
1d14ffa9 | 428 | } |
7d6948cd | 429 | core->hw.samples = core->bufferCount * core->audioDevicePropertyBufferFrameSize; |
1d14ffa9 | 430 | |
1d14ffa9 | 431 | /* set Samplerate */ |
95a860f6 | 432 | status = coreaudio_set_streamformat(core->outputDeviceID, |
986bdbc6 | 433 | &streamBasicDescription); |
3ba6e3f6 AO |
434 | if (status == kAudioHardwareBadObjectError) { |
435 | return 0; | |
436 | } | |
1d14ffa9 | 437 | if (status != kAudioHardwareNoError) { |
7d6948cd AO |
438 | coreaudio_playback_logerr (status, |
439 | "Could not set samplerate %lf\n", | |
986bdbc6 | 440 | streamBasicDescription.mSampleRate); |
1d14ffa9 | 441 | core->outputDeviceID = kAudioDeviceUnknown; |
7d6948cd | 442 | return status; |
1d14ffa9 FB |
443 | } |
444 | ||
eb1a35e4 AO |
445 | /* |
446 | * set Callback. | |
447 | * | |
448 | * On macOS 11.3.1, Core Audio calls AudioDeviceIOProc after calling an | |
449 | * internal function named HALB_Mutex::Lock(), which locks a mutex in | |
450 | * HALB_IOThread::Entry(void*). HALB_Mutex::Lock() is also called in | |
451 | * AudioObjectGetPropertyData, which is called by coreaudio driver. | |
452 | * Therefore, the specified callback must be designed to avoid a deadlock | |
453 | * with the callers of AudioObjectGetPropertyData. | |
454 | */ | |
2f79a18f PM |
455 | core->ioprocid = NULL; |
456 | status = AudioDeviceCreateIOProcID(core->outputDeviceID, | |
457 | audioDeviceIOProc, | |
7d6948cd | 458 | &core->hw, |
2f79a18f | 459 | &core->ioprocid); |
3ba6e3f6 AO |
460 | if (status == kAudioHardwareBadDeviceError) { |
461 | return 0; | |
462 | } | |
2f79a18f | 463 | if (status != kAudioHardwareNoError || core->ioprocid == NULL) { |
7d6948cd | 464 | coreaudio_playback_logerr (status, "Could not set IOProc\n"); |
1d14ffa9 | 465 | core->outputDeviceID = kAudioDeviceUnknown; |
7d6948cd | 466 | return status; |
1d14ffa9 FB |
467 | } |
468 | ||
1d14ffa9 FB |
469 | return 0; |
470 | } | |
471 | ||
7d6948cd | 472 | static void fini_out_device(coreaudioVoiceOut *core) |
1d14ffa9 FB |
473 | { |
474 | OSStatus status; | |
3ba6e3f6 | 475 | UInt32 isrunning; |
1d14ffa9 | 476 | |
ceb1165e | 477 | /* stop playback */ |
3ba6e3f6 AO |
478 | status = coreaudio_get_isrunning(core->outputDeviceID, &isrunning); |
479 | if (status != kAudioHardwareBadObjectError) { | |
5e941d4b | 480 | if (status != kAudioHardwareNoError) { |
3ba6e3f6 AO |
481 | coreaudio_logerr(status, |
482 | "Could not determine whether Device is playing\n"); | |
483 | } | |
484 | ||
485 | if (isrunning) { | |
486 | status = AudioDeviceStop(core->outputDeviceID, core->ioprocid); | |
487 | if (status != kAudioHardwareBadDeviceError && status != kAudioHardwareNoError) { | |
488 | coreaudio_logerr(status, "Could not stop playback\n"); | |
489 | } | |
5e941d4b | 490 | } |
1d14ffa9 | 491 | } |
ceb1165e VR |
492 | |
493 | /* remove callback */ | |
494 | status = AudioDeviceDestroyIOProcID(core->outputDeviceID, | |
495 | core->ioprocid); | |
3ba6e3f6 | 496 | if (status != kAudioHardwareBadDeviceError && status != kAudioHardwareNoError) { |
ceb1165e VR |
497 | coreaudio_logerr(status, "Could not remove IOProc\n"); |
498 | } | |
1d14ffa9 | 499 | core->outputDeviceID = kAudioDeviceUnknown; |
7d6948cd AO |
500 | } |
501 | ||
3ba6e3f6 AO |
502 | static void update_device_playback_state(coreaudioVoiceOut *core) |
503 | { | |
504 | OSStatus status; | |
505 | UInt32 isrunning; | |
506 | ||
507 | status = coreaudio_get_isrunning(core->outputDeviceID, &isrunning); | |
508 | if (status != kAudioHardwareNoError) { | |
509 | if (status != kAudioHardwareBadObjectError) { | |
510 | coreaudio_logerr(status, | |
511 | "Could not determine whether Device is playing\n"); | |
512 | } | |
513 | ||
514 | return; | |
515 | } | |
516 | ||
517 | if (core->enabled) { | |
518 | /* start playback */ | |
519 | if (!isrunning) { | |
520 | status = AudioDeviceStart(core->outputDeviceID, core->ioprocid); | |
521 | if (status != kAudioHardwareBadDeviceError && status != kAudioHardwareNoError) { | |
522 | coreaudio_logerr (status, "Could not resume playback\n"); | |
523 | } | |
524 | } | |
525 | } else { | |
526 | /* stop playback */ | |
527 | if (isrunning) { | |
528 | status = AudioDeviceStop(core->outputDeviceID, | |
529 | core->ioprocid); | |
530 | if (status != kAudioHardwareBadDeviceError && status != kAudioHardwareNoError) { | |
531 | coreaudio_logerr(status, "Could not pause playback\n"); | |
532 | } | |
533 | } | |
534 | } | |
535 | } | |
536 | ||
eb1a35e4 | 537 | /* called without iothread lock. */ |
3ba6e3f6 AO |
538 | static OSStatus handle_voice_change( |
539 | AudioObjectID in_object_id, | |
540 | UInt32 in_number_addresses, | |
541 | const AudioObjectPropertyAddress *in_addresses, | |
542 | void *in_client_data) | |
543 | { | |
544 | OSStatus status; | |
545 | coreaudioVoiceOut *core = in_client_data; | |
546 | ||
eb1a35e4 | 547 | qemu_mutex_lock_iothread(); |
3ba6e3f6 AO |
548 | |
549 | if (core->outputDeviceID) { | |
550 | fini_out_device(core); | |
551 | } | |
552 | ||
553 | status = init_out_device(core); | |
554 | if (!status) { | |
555 | update_device_playback_state(core); | |
556 | } | |
557 | ||
eb1a35e4 | 558 | qemu_mutex_unlock_iothread(); |
3ba6e3f6 AO |
559 | return status; |
560 | } | |
561 | ||
7d6948cd AO |
562 | static int coreaudio_init_out(HWVoiceOut *hw, struct audsettings *as, |
563 | void *drv_opaque) | |
564 | { | |
565 | OSStatus status; | |
566 | coreaudioVoiceOut *core = (coreaudioVoiceOut *) hw; | |
567 | int err; | |
568 | Audiodev *dev = drv_opaque; | |
569 | AudiodevCoreaudioPerDirectionOptions *cpdo = dev->u.coreaudio.out; | |
570 | struct audsettings obt_as; | |
571 | ||
572 | /* create mutex */ | |
eb1a35e4 | 573 | err = pthread_mutex_init(&core->buf_mutex, NULL); |
7d6948cd AO |
574 | if (err) { |
575 | dolog("Could not create mutex\nReason: %s\n", strerror (err)); | |
eb1a35e4 | 576 | return -1; |
7d6948cd AO |
577 | } |
578 | ||
579 | obt_as = *as; | |
580 | as = &obt_as; | |
581 | as->fmt = AUDIO_FORMAT_F32; | |
582 | audio_pcm_init_info (&hw->info, as); | |
583 | ||
584 | core->frameSizeSetting = audio_buffer_frames( | |
585 | qapi_AudiodevCoreaudioPerDirectionOptions_base(cpdo), as, 11610); | |
586 | ||
587 | core->bufferCount = cpdo->has_buffer_count ? cpdo->buffer_count : 4; | |
7d6948cd | 588 | |
3ba6e3f6 AO |
589 | status = AudioObjectAddPropertyListener(kAudioObjectSystemObject, |
590 | &voice_addr, handle_voice_change, | |
591 | core); | |
592 | if (status != kAudioHardwareNoError) { | |
593 | coreaudio_playback_logerr (status, | |
594 | "Could not listen to voice property change\n"); | |
eb1a35e4 | 595 | return -1; |
3ba6e3f6 AO |
596 | } |
597 | ||
7d6948cd | 598 | if (init_out_device(core)) { |
eb1a35e4 AO |
599 | status = AudioObjectRemovePropertyListener(kAudioObjectSystemObject, |
600 | &voice_addr, | |
601 | handle_voice_change, | |
602 | core); | |
603 | if (status != kAudioHardwareNoError) { | |
604 | coreaudio_playback_logerr(status, | |
605 | "Could not remove voice property change listener\n"); | |
606 | } | |
7d6948cd AO |
607 | } |
608 | ||
609 | return 0; | |
610 | } | |
611 | ||
612 | static void coreaudio_fini_out (HWVoiceOut *hw) | |
613 | { | |
614 | OSStatus status; | |
615 | int err; | |
616 | coreaudioVoiceOut *core = (coreaudioVoiceOut *) hw; | |
617 | ||
3ba6e3f6 AO |
618 | status = AudioObjectRemovePropertyListener(kAudioObjectSystemObject, |
619 | &voice_addr, | |
620 | handle_voice_change, | |
621 | core); | |
622 | if (status != kAudioHardwareNoError) { | |
623 | coreaudio_logerr(status, "Could not remove voice property change listener\n"); | |
624 | } | |
625 | ||
7d6948cd | 626 | fini_out_device(core); |
1d14ffa9 FB |
627 | |
628 | /* destroy mutex */ | |
eb1a35e4 | 629 | err = pthread_mutex_destroy(&core->buf_mutex); |
1d14ffa9 | 630 | if (err) { |
c0fe3827 | 631 | dolog("Could not destroy mutex\nReason: %s\n", strerror (err)); |
1d14ffa9 FB |
632 | } |
633 | } | |
634 | ||
571a8c52 | 635 | static void coreaudio_enable_out(HWVoiceOut *hw, bool enable) |
1d14ffa9 | 636 | { |
1d14ffa9 FB |
637 | coreaudioVoiceOut *core = (coreaudioVoiceOut *) hw; |
638 | ||
3ba6e3f6 AO |
639 | core->enabled = enable; |
640 | update_device_playback_state(core); | |
1d14ffa9 FB |
641 | } |
642 | ||
71830221 | 643 | static void *coreaudio_audio_init(Audiodev *dev) |
1d14ffa9 | 644 | { |
17c56dc1 | 645 | return dev; |
1d14ffa9 FB |
646 | } |
647 | ||
648 | static void coreaudio_audio_fini (void *opaque) | |
649 | { | |
1d14ffa9 FB |
650 | } |
651 | ||
35f4b58c | 652 | static struct audio_pcm_ops coreaudio_pcm_ops = { |
1dd3e4d1 JQ |
653 | .init_out = coreaudio_init_out, |
654 | .fini_out = coreaudio_fini_out, | |
fdc8c5f4 | 655 | /* wrapper for audio_generic_write */ |
2ceb8240 | 656 | .write = coreaudio_write, |
fdc8c5f4 | 657 | /* wrapper for audio_generic_get_buffer_out */ |
2ceb8240 | 658 | .get_buffer_out = coreaudio_get_buffer_out, |
fdc8c5f4 VR |
659 | /* wrapper for audio_generic_put_buffer_out */ |
660 | .put_buffer_out = coreaudio_put_buffer_out, | |
571a8c52 | 661 | .enable_out = coreaudio_enable_out |
1d14ffa9 FB |
662 | }; |
663 | ||
d3893a39 | 664 | static struct audio_driver coreaudio_audio_driver = { |
bee37f32 JQ |
665 | .name = "coreaudio", |
666 | .descr = "CoreAudio http://developer.apple.com/audio/coreaudio.html", | |
bee37f32 JQ |
667 | .init = coreaudio_audio_init, |
668 | .fini = coreaudio_audio_fini, | |
669 | .pcm_ops = &coreaudio_pcm_ops, | |
670 | .can_be_default = 1, | |
671 | .max_voices_out = 1, | |
672 | .max_voices_in = 0, | |
673 | .voice_size_out = sizeof (coreaudioVoiceOut), | |
674 | .voice_size_in = 0 | |
1d14ffa9 | 675 | }; |
d3893a39 GH |
676 | |
677 | static void register_audio_coreaudio(void) | |
678 | { | |
679 | audio_driver_register(&coreaudio_audio_driver); | |
680 | } | |
681 | type_init(register_audio_coreaudio); |