|
1 /* |
|
2 * Copyright © 2011 Mozilla Foundation |
|
3 * |
|
4 * This program is made available under an ISC-style license. See the |
|
5 * accompanying file LICENSE for details. |
|
6 */ |
|
7 #undef NDEBUG |
|
8 #include <assert.h> |
|
9 #include <pthread.h> |
|
10 #include <stdlib.h> |
|
11 #include <AudioUnit/AudioUnit.h> |
|
12 #include <CoreAudio/AudioHardware.h> |
|
13 #include <CoreAudio/HostTime.h> |
|
14 #include <CoreFoundation/CoreFoundation.h> |
|
15 #include "cubeb/cubeb.h" |
|
16 #include "cubeb-internal.h" |
|
17 |
|
18 #if !defined(kCFCoreFoundationVersionNumber10_7) |
|
19 /* From CoreFoundation CFBase.h */ |
|
20 #define kCFCoreFoundationVersionNumber10_7 635.00 |
|
21 #endif |
|
22 |
|
23 #define CUBEB_STREAM_MAX 16 |
|
24 #define NBUFS 4 |
|
25 |
|
26 static struct cubeb_ops const audiounit_ops; |
|
27 |
|
28 struct cubeb { |
|
29 struct cubeb_ops const * ops; |
|
30 pthread_mutex_t mutex; |
|
31 int active_streams; |
|
32 int limit_streams; |
|
33 }; |
|
34 |
|
35 struct cubeb_stream { |
|
36 cubeb * context; |
|
37 AudioUnit unit; |
|
38 cubeb_data_callback data_callback; |
|
39 cubeb_state_callback state_callback; |
|
40 void * user_ptr; |
|
41 AudioStreamBasicDescription sample_spec; |
|
42 pthread_mutex_t mutex; |
|
43 uint64_t frames_played; |
|
44 uint64_t frames_queued; |
|
45 int shutdown; |
|
46 int draining; |
|
47 uint64_t current_latency_frames; |
|
48 uint64_t hw_latency_frames; |
|
49 }; |
|
50 |
|
51 static int64_t |
|
52 audiotimestamp_to_latency(AudioTimeStamp const * tstamp, cubeb_stream * stream) |
|
53 { |
|
54 if (!(tstamp->mFlags & kAudioTimeStampHostTimeValid)) { |
|
55 return 0; |
|
56 } |
|
57 |
|
58 uint64_t pres = AudioConvertHostTimeToNanos(tstamp->mHostTime); |
|
59 uint64_t now = AudioConvertHostTimeToNanos(AudioGetCurrentHostTime()); |
|
60 |
|
61 return ((pres - now) * stream->sample_spec.mSampleRate) / 1000000000LL; |
|
62 } |
|
63 |
|
64 static OSStatus |
|
65 audiounit_output_callback(void * user_ptr, AudioUnitRenderActionFlags * flags, |
|
66 AudioTimeStamp const * tstamp, UInt32 bus, UInt32 nframes, |
|
67 AudioBufferList * bufs) |
|
68 { |
|
69 cubeb_stream * stm; |
|
70 unsigned char * buf; |
|
71 long got; |
|
72 OSStatus r; |
|
73 |
|
74 assert(bufs->mNumberBuffers == 1); |
|
75 buf = bufs->mBuffers[0].mData; |
|
76 |
|
77 stm = user_ptr; |
|
78 |
|
79 pthread_mutex_lock(&stm->mutex); |
|
80 |
|
81 stm->current_latency_frames = audiotimestamp_to_latency(tstamp, stm); |
|
82 |
|
83 if (stm->draining || stm->shutdown) { |
|
84 pthread_mutex_unlock(&stm->mutex); |
|
85 if (stm->draining) { |
|
86 r = AudioOutputUnitStop(stm->unit); |
|
87 assert(r == 0); |
|
88 stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_DRAINED); |
|
89 } |
|
90 return noErr; |
|
91 } |
|
92 |
|
93 pthread_mutex_unlock(&stm->mutex); |
|
94 got = stm->data_callback(stm, stm->user_ptr, buf, nframes); |
|
95 pthread_mutex_lock(&stm->mutex); |
|
96 if (got < 0) { |
|
97 /* XXX handle this case. */ |
|
98 assert(false); |
|
99 pthread_mutex_unlock(&stm->mutex); |
|
100 return noErr; |
|
101 } |
|
102 |
|
103 if ((UInt32) got < nframes) { |
|
104 size_t got_bytes = got * stm->sample_spec.mBytesPerFrame; |
|
105 size_t rem_bytes = (nframes - got) * stm->sample_spec.mBytesPerFrame; |
|
106 |
|
107 stm->draining = 1; |
|
108 |
|
109 memset(buf + got_bytes, 0, rem_bytes); |
|
110 } |
|
111 |
|
112 stm->frames_played = stm->frames_queued; |
|
113 stm->frames_queued += got; |
|
114 pthread_mutex_unlock(&stm->mutex); |
|
115 |
|
116 return noErr; |
|
117 } |
|
118 |
|
119 /*static*/ int |
|
120 audiounit_init(cubeb ** context, char const * context_name) |
|
121 { |
|
122 cubeb * ctx; |
|
123 int r; |
|
124 |
|
125 *context = NULL; |
|
126 |
|
127 ctx = calloc(1, sizeof(*ctx)); |
|
128 assert(ctx); |
|
129 |
|
130 ctx->ops = &audiounit_ops; |
|
131 |
|
132 r = pthread_mutex_init(&ctx->mutex, NULL); |
|
133 assert(r == 0); |
|
134 |
|
135 ctx->active_streams = 0; |
|
136 |
|
137 ctx->limit_streams = kCFCoreFoundationVersionNumber < kCFCoreFoundationVersionNumber10_7; |
|
138 |
|
139 *context = ctx; |
|
140 |
|
141 return CUBEB_OK; |
|
142 } |
|
143 |
|
144 static char const * |
|
145 audiounit_get_backend_id(cubeb * ctx) |
|
146 { |
|
147 return "audiounit"; |
|
148 } |
|
149 |
|
150 static int |
|
151 audiounit_get_output_device_id(AudioDeviceID * device_id) |
|
152 { |
|
153 UInt32 size; |
|
154 OSStatus r; |
|
155 AudioObjectPropertyAddress output_device_address = { |
|
156 kAudioHardwarePropertyDefaultOutputDevice, |
|
157 kAudioObjectPropertyScopeGlobal, |
|
158 kAudioObjectPropertyElementMaster |
|
159 }; |
|
160 |
|
161 size = sizeof(*device_id); |
|
162 |
|
163 r = AudioObjectGetPropertyData(kAudioObjectSystemObject, |
|
164 &output_device_address, |
|
165 0, |
|
166 NULL, |
|
167 &size, |
|
168 device_id); |
|
169 if (r != noErr) { |
|
170 return CUBEB_ERROR; |
|
171 } |
|
172 |
|
173 return CUBEB_OK; |
|
174 } |
|
175 |
|
176 /* Get the acceptable buffer size (in frames) that this device can work with. */ |
|
177 static int |
|
178 audiounit_get_acceptable_latency_range(AudioValueRange * latency_range) |
|
179 { |
|
180 UInt32 size; |
|
181 OSStatus r; |
|
182 AudioDeviceID output_device_id; |
|
183 AudioObjectPropertyAddress output_device_buffer_size_range = { |
|
184 kAudioDevicePropertyBufferFrameSizeRange, |
|
185 kAudioDevicePropertyScopeOutput, |
|
186 kAudioObjectPropertyElementMaster |
|
187 }; |
|
188 |
|
189 if (audiounit_get_output_device_id(&output_device_id) != CUBEB_OK) { |
|
190 return CUBEB_ERROR; |
|
191 } |
|
192 |
|
193 /* Get the buffer size range this device supports */ |
|
194 size = sizeof(*latency_range); |
|
195 |
|
196 r = AudioObjectGetPropertyData(output_device_id, |
|
197 &output_device_buffer_size_range, |
|
198 0, |
|
199 NULL, |
|
200 &size, |
|
201 latency_range); |
|
202 if (r != noErr) { |
|
203 return CUBEB_ERROR; |
|
204 } |
|
205 |
|
206 return CUBEB_OK; |
|
207 } |
|
208 |
|
209 int |
|
210 audiounit_get_max_channel_count(cubeb * ctx, uint32_t * max_channels) |
|
211 { |
|
212 UInt32 size; |
|
213 OSStatus r; |
|
214 AudioDeviceID output_device_id; |
|
215 AudioStreamBasicDescription stream_format; |
|
216 AudioObjectPropertyAddress stream_format_address = { |
|
217 kAudioDevicePropertyStreamFormat, |
|
218 kAudioDevicePropertyScopeOutput, |
|
219 kAudioObjectPropertyElementMaster |
|
220 }; |
|
221 |
|
222 assert(ctx && max_channels); |
|
223 |
|
224 if (audiounit_get_output_device_id(&output_device_id) != CUBEB_OK) { |
|
225 return CUBEB_ERROR; |
|
226 } |
|
227 |
|
228 size = sizeof(stream_format); |
|
229 |
|
230 r = AudioObjectGetPropertyData(output_device_id, |
|
231 &stream_format_address, |
|
232 0, |
|
233 NULL, |
|
234 &size, |
|
235 &stream_format); |
|
236 if (r != noErr) { |
|
237 return CUBEB_ERROR; |
|
238 } |
|
239 |
|
240 *max_channels = stream_format.mChannelsPerFrame; |
|
241 |
|
242 return CUBEB_OK; |
|
243 } |
|
244 |
|
245 static int |
|
246 audiounit_get_min_latency(cubeb * ctx, cubeb_stream_params params, uint32_t * latency_ms) |
|
247 { |
|
248 AudioValueRange latency_range; |
|
249 |
|
250 if (audiounit_get_acceptable_latency_range(&latency_range) != CUBEB_OK) { |
|
251 return CUBEB_ERROR; |
|
252 } |
|
253 |
|
254 *latency_ms = (latency_range.mMinimum * 1000 + params.rate - 1) / params.rate; |
|
255 |
|
256 return CUBEB_OK; |
|
257 } |
|
258 |
|
259 static int |
|
260 audiounit_get_preferred_sample_rate(cubeb * ctx, uint32_t * rate) |
|
261 { |
|
262 UInt32 size; |
|
263 OSStatus r; |
|
264 Float64 fsamplerate; |
|
265 AudioDeviceID output_device_id; |
|
266 AudioObjectPropertyAddress samplerate_address = { |
|
267 kAudioDevicePropertyNominalSampleRate, |
|
268 kAudioObjectPropertyScopeGlobal, |
|
269 kAudioObjectPropertyElementMaster |
|
270 }; |
|
271 |
|
272 if (audiounit_get_output_device_id(&output_device_id) != CUBEB_OK) { |
|
273 return CUBEB_ERROR; |
|
274 } |
|
275 |
|
276 size = sizeof(fsamplerate); |
|
277 r = AudioObjectGetPropertyData(output_device_id, |
|
278 &samplerate_address, |
|
279 0, |
|
280 NULL, |
|
281 &size, |
|
282 &fsamplerate); |
|
283 |
|
284 if (r != noErr) { |
|
285 return CUBEB_ERROR; |
|
286 } |
|
287 |
|
288 *rate = (uint32_t)fsamplerate; |
|
289 |
|
290 return CUBEB_OK; |
|
291 } |
|
292 |
|
293 static void |
|
294 audiounit_destroy(cubeb * ctx) |
|
295 { |
|
296 int r; |
|
297 |
|
298 assert(ctx->active_streams == 0); |
|
299 |
|
300 r = pthread_mutex_destroy(&ctx->mutex); |
|
301 assert(r == 0); |
|
302 |
|
303 free(ctx); |
|
304 } |
|
305 |
|
306 static void audiounit_stream_destroy(cubeb_stream * stm); |
|
307 |
|
308 static int |
|
309 audiounit_stream_init(cubeb * context, cubeb_stream ** stream, char const * stream_name, |
|
310 cubeb_stream_params stream_params, unsigned int latency, |
|
311 cubeb_data_callback data_callback, cubeb_state_callback state_callback, |
|
312 void * user_ptr) |
|
313 { |
|
314 AudioStreamBasicDescription ss; |
|
315 #if MAC_OS_X_VERSION_MIN_REQUIRED < 1060 |
|
316 ComponentDescription desc; |
|
317 Component comp; |
|
318 #else |
|
319 AudioComponentDescription desc; |
|
320 AudioComponent comp; |
|
321 #endif |
|
322 cubeb_stream * stm; |
|
323 AURenderCallbackStruct input; |
|
324 unsigned int buffer_size, default_buffer_size; |
|
325 OSStatus r; |
|
326 UInt32 size; |
|
327 AudioDeviceID output_device_id; |
|
328 AudioValueRange latency_range; |
|
329 |
|
330 assert(context); |
|
331 *stream = NULL; |
|
332 |
|
333 memset(&ss, 0, sizeof(ss)); |
|
334 ss.mFormatFlags = 0; |
|
335 |
|
336 switch (stream_params.format) { |
|
337 case CUBEB_SAMPLE_S16LE: |
|
338 ss.mBitsPerChannel = 16; |
|
339 ss.mFormatFlags |= kAudioFormatFlagIsSignedInteger; |
|
340 break; |
|
341 case CUBEB_SAMPLE_S16BE: |
|
342 ss.mBitsPerChannel = 16; |
|
343 ss.mFormatFlags |= kAudioFormatFlagIsSignedInteger | |
|
344 kAudioFormatFlagIsBigEndian; |
|
345 break; |
|
346 case CUBEB_SAMPLE_FLOAT32LE: |
|
347 ss.mBitsPerChannel = 32; |
|
348 ss.mFormatFlags |= kAudioFormatFlagIsFloat; |
|
349 break; |
|
350 case CUBEB_SAMPLE_FLOAT32BE: |
|
351 ss.mBitsPerChannel = 32; |
|
352 ss.mFormatFlags |= kAudioFormatFlagIsFloat | |
|
353 kAudioFormatFlagIsBigEndian; |
|
354 break; |
|
355 default: |
|
356 return CUBEB_ERROR_INVALID_FORMAT; |
|
357 } |
|
358 |
|
359 ss.mFormatID = kAudioFormatLinearPCM; |
|
360 ss.mFormatFlags |= kLinearPCMFormatFlagIsPacked; |
|
361 ss.mSampleRate = stream_params.rate; |
|
362 ss.mChannelsPerFrame = stream_params.channels; |
|
363 |
|
364 ss.mBytesPerFrame = (ss.mBitsPerChannel / 8) * ss.mChannelsPerFrame; |
|
365 ss.mFramesPerPacket = 1; |
|
366 ss.mBytesPerPacket = ss.mBytesPerFrame * ss.mFramesPerPacket; |
|
367 |
|
368 pthread_mutex_lock(&context->mutex); |
|
369 if (context->limit_streams && context->active_streams >= CUBEB_STREAM_MAX) { |
|
370 pthread_mutex_unlock(&context->mutex); |
|
371 return CUBEB_ERROR; |
|
372 } |
|
373 context->active_streams += 1; |
|
374 pthread_mutex_unlock(&context->mutex); |
|
375 |
|
376 desc.componentType = kAudioUnitType_Output; |
|
377 desc.componentSubType = kAudioUnitSubType_DefaultOutput; |
|
378 desc.componentManufacturer = kAudioUnitManufacturer_Apple; |
|
379 desc.componentFlags = 0; |
|
380 desc.componentFlagsMask = 0; |
|
381 #if MAC_OS_X_VERSION_MIN_REQUIRED < 1060 |
|
382 comp = FindNextComponent(NULL, &desc); |
|
383 #else |
|
384 comp = AudioComponentFindNext(NULL, &desc); |
|
385 #endif |
|
386 assert(comp); |
|
387 |
|
388 stm = calloc(1, sizeof(*stm)); |
|
389 assert(stm); |
|
390 |
|
391 stm->context = context; |
|
392 stm->data_callback = data_callback; |
|
393 stm->state_callback = state_callback; |
|
394 stm->user_ptr = user_ptr; |
|
395 |
|
396 stm->sample_spec = ss; |
|
397 |
|
398 r = pthread_mutex_init(&stm->mutex, NULL); |
|
399 assert(r == 0); |
|
400 |
|
401 stm->frames_played = 0; |
|
402 stm->frames_queued = 0; |
|
403 stm->current_latency_frames = 0; |
|
404 stm->hw_latency_frames = UINT64_MAX; |
|
405 |
|
406 #if MAC_OS_X_VERSION_MIN_REQUIRED < 1060 |
|
407 r = OpenAComponent(comp, &stm->unit); |
|
408 #else |
|
409 r = AudioComponentInstanceNew(comp, &stm->unit); |
|
410 #endif |
|
411 if (r != 0) { |
|
412 audiounit_stream_destroy(stm); |
|
413 return CUBEB_ERROR; |
|
414 } |
|
415 |
|
416 input.inputProc = audiounit_output_callback; |
|
417 input.inputProcRefCon = stm; |
|
418 r = AudioUnitSetProperty(stm->unit, kAudioUnitProperty_SetRenderCallback, |
|
419 kAudioUnitScope_Global, 0, &input, sizeof(input)); |
|
420 if (r != 0) { |
|
421 audiounit_stream_destroy(stm); |
|
422 return CUBEB_ERROR; |
|
423 } |
|
424 |
|
425 buffer_size = latency / 1000.0 * ss.mSampleRate; |
|
426 |
|
427 /* Get the range of latency this particular device can work with, and clamp |
|
428 * the requested latency to this acceptable range. */ |
|
429 if (audiounit_get_acceptable_latency_range(&latency_range) != CUBEB_OK) { |
|
430 audiounit_stream_destroy(stm); |
|
431 return CUBEB_ERROR; |
|
432 } |
|
433 |
|
434 if (buffer_size < (unsigned int) latency_range.mMinimum) { |
|
435 buffer_size = (unsigned int) latency_range.mMinimum; |
|
436 } else if (buffer_size > (unsigned int) latency_range.mMaximum) { |
|
437 buffer_size = (unsigned int) latency_range.mMaximum; |
|
438 } |
|
439 |
|
440 /** |
|
441 * Get the default buffer size. If our latency request is below the default, |
|
442 * set it. Otherwise, use the default latency. |
|
443 **/ |
|
444 size = sizeof(default_buffer_size); |
|
445 r = AudioUnitGetProperty(stm->unit, kAudioDevicePropertyBufferFrameSize, |
|
446 kAudioUnitScope_Output, 0, &default_buffer_size, &size); |
|
447 |
|
448 if (r != 0) { |
|
449 audiounit_stream_destroy(stm); |
|
450 return CUBEB_ERROR; |
|
451 } |
|
452 |
|
453 // Setting the latency doesn't work well for USB headsets (eg. plantronics). |
|
454 // Keep the default latency for now. |
|
455 #if 0 |
|
456 if (buffer_size < default_buffer_size) { |
|
457 /* Set the maximum number of frame that the render callback will ask for, |
|
458 * effectively setting the latency of the stream. This is process-wide. */ |
|
459 r = AudioUnitSetProperty(stm->unit, kAudioDevicePropertyBufferFrameSize, |
|
460 kAudioUnitScope_Output, 0, &buffer_size, sizeof(buffer_size)); |
|
461 if (r != 0) { |
|
462 audiounit_stream_destroy(stm); |
|
463 return CUBEB_ERROR; |
|
464 } |
|
465 } |
|
466 #endif |
|
467 |
|
468 r = AudioUnitSetProperty(stm->unit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, |
|
469 0, &ss, sizeof(ss)); |
|
470 if (r != 0) { |
|
471 audiounit_stream_destroy(stm); |
|
472 return CUBEB_ERROR; |
|
473 } |
|
474 |
|
475 r = AudioUnitInitialize(stm->unit); |
|
476 if (r != 0) { |
|
477 audiounit_stream_destroy(stm); |
|
478 return CUBEB_ERROR; |
|
479 } |
|
480 |
|
481 *stream = stm; |
|
482 |
|
483 return CUBEB_OK; |
|
484 } |
|
485 |
|
486 static void |
|
487 audiounit_stream_destroy(cubeb_stream * stm) |
|
488 { |
|
489 int r; |
|
490 |
|
491 stm->shutdown = 1; |
|
492 |
|
493 if (stm->unit) { |
|
494 AudioOutputUnitStop(stm->unit); |
|
495 AudioUnitUninitialize(stm->unit); |
|
496 #if MAC_OS_X_VERSION_MIN_REQUIRED < 1060 |
|
497 CloseComponent(stm->unit); |
|
498 #else |
|
499 AudioComponentInstanceDispose(stm->unit); |
|
500 #endif |
|
501 } |
|
502 |
|
503 r = pthread_mutex_destroy(&stm->mutex); |
|
504 assert(r == 0); |
|
505 |
|
506 pthread_mutex_lock(&stm->context->mutex); |
|
507 assert(stm->context->active_streams >= 1); |
|
508 stm->context->active_streams -= 1; |
|
509 pthread_mutex_unlock(&stm->context->mutex); |
|
510 |
|
511 free(stm); |
|
512 } |
|
513 |
|
514 static int |
|
515 audiounit_stream_start(cubeb_stream * stm) |
|
516 { |
|
517 OSStatus r; |
|
518 r = AudioOutputUnitStart(stm->unit); |
|
519 assert(r == 0); |
|
520 stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STARTED); |
|
521 return CUBEB_OK; |
|
522 } |
|
523 |
|
524 static int |
|
525 audiounit_stream_stop(cubeb_stream * stm) |
|
526 { |
|
527 OSStatus r; |
|
528 r = AudioOutputUnitStop(stm->unit); |
|
529 assert(r == 0); |
|
530 stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STOPPED); |
|
531 return CUBEB_OK; |
|
532 } |
|
533 |
|
534 static int |
|
535 audiounit_stream_get_position(cubeb_stream * stm, uint64_t * position) |
|
536 { |
|
537 pthread_mutex_lock(&stm->mutex); |
|
538 *position = stm->frames_played; |
|
539 pthread_mutex_unlock(&stm->mutex); |
|
540 return CUBEB_OK; |
|
541 } |
|
542 |
|
543 int |
|
544 audiounit_stream_get_latency(cubeb_stream * stm, uint32_t * latency) |
|
545 { |
|
546 pthread_mutex_lock(&stm->mutex); |
|
547 if (stm->hw_latency_frames == UINT64_MAX) { |
|
548 UInt32 size; |
|
549 uint32_t device_latency_frames, device_safety_offset; |
|
550 double unit_latency_sec; |
|
551 AudioDeviceID output_device_id; |
|
552 OSStatus r; |
|
553 AudioObjectPropertyAddress latency_address = { |
|
554 kAudioDevicePropertyLatency, |
|
555 kAudioDevicePropertyScopeOutput, |
|
556 kAudioObjectPropertyElementMaster |
|
557 }; |
|
558 AudioObjectPropertyAddress safety_offset_address = { |
|
559 kAudioDevicePropertySafetyOffset, |
|
560 kAudioDevicePropertyScopeOutput, |
|
561 kAudioObjectPropertyElementMaster |
|
562 }; |
|
563 |
|
564 r = audiounit_get_output_device_id(&output_device_id); |
|
565 |
|
566 if (r != noErr) { |
|
567 pthread_mutex_unlock(&stm->mutex); |
|
568 return CUBEB_ERROR; |
|
569 } |
|
570 |
|
571 size = sizeof(unit_latency_sec); |
|
572 r = AudioUnitGetProperty(stm->unit, |
|
573 kAudioUnitProperty_Latency, |
|
574 kAudioUnitScope_Global, |
|
575 0, |
|
576 &unit_latency_sec, |
|
577 &size); |
|
578 if (r != noErr) { |
|
579 pthread_mutex_unlock(&stm->mutex); |
|
580 return CUBEB_ERROR; |
|
581 } |
|
582 |
|
583 size = sizeof(device_latency_frames); |
|
584 r = AudioObjectGetPropertyData(output_device_id, |
|
585 &latency_address, |
|
586 0, |
|
587 NULL, |
|
588 &size, |
|
589 &device_latency_frames); |
|
590 if (r != noErr) { |
|
591 pthread_mutex_unlock(&stm->mutex); |
|
592 return CUBEB_ERROR; |
|
593 } |
|
594 |
|
595 size = sizeof(device_safety_offset); |
|
596 r = AudioObjectGetPropertyData(output_device_id, |
|
597 &safety_offset_address, |
|
598 0, |
|
599 NULL, |
|
600 &size, |
|
601 &device_safety_offset); |
|
602 if (r != noErr) { |
|
603 pthread_mutex_unlock(&stm->mutex); |
|
604 return CUBEB_ERROR; |
|
605 } |
|
606 |
|
607 /* This part is fixed and depend on the stream parameter and the hardware. */ |
|
608 stm->hw_latency_frames = |
|
609 (uint32_t)(unit_latency_sec * stm->sample_spec.mSampleRate) |
|
610 + device_latency_frames |
|
611 + device_safety_offset; |
|
612 } |
|
613 |
|
614 *latency = stm->hw_latency_frames + stm->current_latency_frames; |
|
615 pthread_mutex_unlock(&stm->mutex); |
|
616 |
|
617 return CUBEB_OK; |
|
618 } |
|
619 |
|
620 static struct cubeb_ops const audiounit_ops = { |
|
621 .init = audiounit_init, |
|
622 .get_backend_id = audiounit_get_backend_id, |
|
623 .get_max_channel_count = audiounit_get_max_channel_count, |
|
624 .get_min_latency = audiounit_get_min_latency, |
|
625 .get_preferred_sample_rate = audiounit_get_preferred_sample_rate, |
|
626 .destroy = audiounit_destroy, |
|
627 .stream_init = audiounit_stream_init, |
|
628 .stream_destroy = audiounit_stream_destroy, |
|
629 .stream_start = audiounit_stream_start, |
|
630 .stream_stop = audiounit_stream_stop, |
|
631 .stream_get_position = audiounit_stream_get_position, |
|
632 .stream_get_latency = audiounit_stream_get_latency |
|
633 }; |