1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/media/libcubeb/src/cubeb_audiounit.c Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,633 @@ 1.4 +/* 1.5 + * Copyright © 2011 Mozilla Foundation 1.6 + * 1.7 + * This program is made available under an ISC-style license. See the 1.8 + * accompanying file LICENSE for details. 1.9 + */ 1.10 +#undef NDEBUG 1.11 +#include <assert.h> 1.12 +#include <pthread.h> 1.13 +#include <stdlib.h> 1.14 +#include <AudioUnit/AudioUnit.h> 1.15 +#include <CoreAudio/AudioHardware.h> 1.16 +#include <CoreAudio/HostTime.h> 1.17 +#include <CoreFoundation/CoreFoundation.h> 1.18 +#include "cubeb/cubeb.h" 1.19 +#include "cubeb-internal.h" 1.20 + 1.21 +#if !defined(kCFCoreFoundationVersionNumber10_7) 1.22 +/* From CoreFoundation CFBase.h */ 1.23 +#define kCFCoreFoundationVersionNumber10_7 635.00 1.24 +#endif 1.25 + 1.26 +#define CUBEB_STREAM_MAX 16 1.27 +#define NBUFS 4 1.28 + 1.29 +static struct cubeb_ops const audiounit_ops; 1.30 + 1.31 +struct cubeb { 1.32 + struct cubeb_ops const * ops; 1.33 + pthread_mutex_t mutex; 1.34 + int active_streams; 1.35 + int limit_streams; 1.36 +}; 1.37 + 1.38 +struct cubeb_stream { 1.39 + cubeb * context; 1.40 + AudioUnit unit; 1.41 + cubeb_data_callback data_callback; 1.42 + cubeb_state_callback state_callback; 1.43 + void * user_ptr; 1.44 + AudioStreamBasicDescription sample_spec; 1.45 + pthread_mutex_t mutex; 1.46 + uint64_t frames_played; 1.47 + uint64_t frames_queued; 1.48 + int shutdown; 1.49 + int draining; 1.50 + uint64_t current_latency_frames; 1.51 + uint64_t hw_latency_frames; 1.52 +}; 1.53 + 1.54 +static int64_t 1.55 +audiotimestamp_to_latency(AudioTimeStamp const * tstamp, cubeb_stream * stream) 1.56 +{ 1.57 + if (!(tstamp->mFlags & kAudioTimeStampHostTimeValid)) { 1.58 + return 0; 1.59 + } 1.60 + 1.61 + uint64_t pres = AudioConvertHostTimeToNanos(tstamp->mHostTime); 1.62 + uint64_t now = AudioConvertHostTimeToNanos(AudioGetCurrentHostTime()); 1.63 + 1.64 + return ((pres - now) * stream->sample_spec.mSampleRate) / 1000000000LL; 1.65 +} 1.66 + 1.67 +static OSStatus 1.68 +audiounit_output_callback(void * user_ptr, AudioUnitRenderActionFlags * flags, 1.69 + AudioTimeStamp const * tstamp, UInt32 bus, UInt32 nframes, 1.70 + AudioBufferList * bufs) 1.71 +{ 1.72 + cubeb_stream * stm; 1.73 + unsigned char * buf; 1.74 + long got; 1.75 + OSStatus r; 1.76 + 1.77 + assert(bufs->mNumberBuffers == 1); 1.78 + buf = bufs->mBuffers[0].mData; 1.79 + 1.80 + stm = user_ptr; 1.81 + 1.82 + pthread_mutex_lock(&stm->mutex); 1.83 + 1.84 + stm->current_latency_frames = audiotimestamp_to_latency(tstamp, stm); 1.85 + 1.86 + if (stm->draining || stm->shutdown) { 1.87 + pthread_mutex_unlock(&stm->mutex); 1.88 + if (stm->draining) { 1.89 + r = AudioOutputUnitStop(stm->unit); 1.90 + assert(r == 0); 1.91 + stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_DRAINED); 1.92 + } 1.93 + return noErr; 1.94 + } 1.95 + 1.96 + pthread_mutex_unlock(&stm->mutex); 1.97 + got = stm->data_callback(stm, stm->user_ptr, buf, nframes); 1.98 + pthread_mutex_lock(&stm->mutex); 1.99 + if (got < 0) { 1.100 + /* XXX handle this case. */ 1.101 + assert(false); 1.102 + pthread_mutex_unlock(&stm->mutex); 1.103 + return noErr; 1.104 + } 1.105 + 1.106 + if ((UInt32) got < nframes) { 1.107 + size_t got_bytes = got * stm->sample_spec.mBytesPerFrame; 1.108 + size_t rem_bytes = (nframes - got) * stm->sample_spec.mBytesPerFrame; 1.109 + 1.110 + stm->draining = 1; 1.111 + 1.112 + memset(buf + got_bytes, 0, rem_bytes); 1.113 + } 1.114 + 1.115 + stm->frames_played = stm->frames_queued; 1.116 + stm->frames_queued += got; 1.117 + pthread_mutex_unlock(&stm->mutex); 1.118 + 1.119 + return noErr; 1.120 +} 1.121 + 1.122 +/*static*/ int 1.123 +audiounit_init(cubeb ** context, char const * context_name) 1.124 +{ 1.125 + cubeb * ctx; 1.126 + int r; 1.127 + 1.128 + *context = NULL; 1.129 + 1.130 + ctx = calloc(1, sizeof(*ctx)); 1.131 + assert(ctx); 1.132 + 1.133 + ctx->ops = &audiounit_ops; 1.134 + 1.135 + r = pthread_mutex_init(&ctx->mutex, NULL); 1.136 + assert(r == 0); 1.137 + 1.138 + ctx->active_streams = 0; 1.139 + 1.140 + ctx->limit_streams = kCFCoreFoundationVersionNumber < kCFCoreFoundationVersionNumber10_7; 1.141 + 1.142 + *context = ctx; 1.143 + 1.144 + return CUBEB_OK; 1.145 +} 1.146 + 1.147 +static char const * 1.148 +audiounit_get_backend_id(cubeb * ctx) 1.149 +{ 1.150 + return "audiounit"; 1.151 +} 1.152 + 1.153 +static int 1.154 +audiounit_get_output_device_id(AudioDeviceID * device_id) 1.155 +{ 1.156 + UInt32 size; 1.157 + OSStatus r; 1.158 + AudioObjectPropertyAddress output_device_address = { 1.159 + kAudioHardwarePropertyDefaultOutputDevice, 1.160 + kAudioObjectPropertyScopeGlobal, 1.161 + kAudioObjectPropertyElementMaster 1.162 + }; 1.163 + 1.164 + size = sizeof(*device_id); 1.165 + 1.166 + r = AudioObjectGetPropertyData(kAudioObjectSystemObject, 1.167 + &output_device_address, 1.168 + 0, 1.169 + NULL, 1.170 + &size, 1.171 + device_id); 1.172 + if (r != noErr) { 1.173 + return CUBEB_ERROR; 1.174 + } 1.175 + 1.176 + return CUBEB_OK; 1.177 +} 1.178 + 1.179 +/* Get the acceptable buffer size (in frames) that this device can work with. */ 1.180 +static int 1.181 +audiounit_get_acceptable_latency_range(AudioValueRange * latency_range) 1.182 +{ 1.183 + UInt32 size; 1.184 + OSStatus r; 1.185 + AudioDeviceID output_device_id; 1.186 + AudioObjectPropertyAddress output_device_buffer_size_range = { 1.187 + kAudioDevicePropertyBufferFrameSizeRange, 1.188 + kAudioDevicePropertyScopeOutput, 1.189 + kAudioObjectPropertyElementMaster 1.190 + }; 1.191 + 1.192 + if (audiounit_get_output_device_id(&output_device_id) != CUBEB_OK) { 1.193 + return CUBEB_ERROR; 1.194 + } 1.195 + 1.196 + /* Get the buffer size range this device supports */ 1.197 + size = sizeof(*latency_range); 1.198 + 1.199 + r = AudioObjectGetPropertyData(output_device_id, 1.200 + &output_device_buffer_size_range, 1.201 + 0, 1.202 + NULL, 1.203 + &size, 1.204 + latency_range); 1.205 + if (r != noErr) { 1.206 + return CUBEB_ERROR; 1.207 + } 1.208 + 1.209 + return CUBEB_OK; 1.210 +} 1.211 + 1.212 +int 1.213 +audiounit_get_max_channel_count(cubeb * ctx, uint32_t * max_channels) 1.214 +{ 1.215 + UInt32 size; 1.216 + OSStatus r; 1.217 + AudioDeviceID output_device_id; 1.218 + AudioStreamBasicDescription stream_format; 1.219 + AudioObjectPropertyAddress stream_format_address = { 1.220 + kAudioDevicePropertyStreamFormat, 1.221 + kAudioDevicePropertyScopeOutput, 1.222 + kAudioObjectPropertyElementMaster 1.223 + }; 1.224 + 1.225 + assert(ctx && max_channels); 1.226 + 1.227 + if (audiounit_get_output_device_id(&output_device_id) != CUBEB_OK) { 1.228 + return CUBEB_ERROR; 1.229 + } 1.230 + 1.231 + size = sizeof(stream_format); 1.232 + 1.233 + r = AudioObjectGetPropertyData(output_device_id, 1.234 + &stream_format_address, 1.235 + 0, 1.236 + NULL, 1.237 + &size, 1.238 + &stream_format); 1.239 + if (r != noErr) { 1.240 + return CUBEB_ERROR; 1.241 + } 1.242 + 1.243 + *max_channels = stream_format.mChannelsPerFrame; 1.244 + 1.245 + return CUBEB_OK; 1.246 +} 1.247 + 1.248 +static int 1.249 +audiounit_get_min_latency(cubeb * ctx, cubeb_stream_params params, uint32_t * latency_ms) 1.250 +{ 1.251 + AudioValueRange latency_range; 1.252 + 1.253 + if (audiounit_get_acceptable_latency_range(&latency_range) != CUBEB_OK) { 1.254 + return CUBEB_ERROR; 1.255 + } 1.256 + 1.257 + *latency_ms = (latency_range.mMinimum * 1000 + params.rate - 1) / params.rate; 1.258 + 1.259 + return CUBEB_OK; 1.260 +} 1.261 + 1.262 +static int 1.263 +audiounit_get_preferred_sample_rate(cubeb * ctx, uint32_t * rate) 1.264 +{ 1.265 + UInt32 size; 1.266 + OSStatus r; 1.267 + Float64 fsamplerate; 1.268 + AudioDeviceID output_device_id; 1.269 + AudioObjectPropertyAddress samplerate_address = { 1.270 + kAudioDevicePropertyNominalSampleRate, 1.271 + kAudioObjectPropertyScopeGlobal, 1.272 + kAudioObjectPropertyElementMaster 1.273 + }; 1.274 + 1.275 + if (audiounit_get_output_device_id(&output_device_id) != CUBEB_OK) { 1.276 + return CUBEB_ERROR; 1.277 + } 1.278 + 1.279 + size = sizeof(fsamplerate); 1.280 + r = AudioObjectGetPropertyData(output_device_id, 1.281 + &samplerate_address, 1.282 + 0, 1.283 + NULL, 1.284 + &size, 1.285 + &fsamplerate); 1.286 + 1.287 + if (r != noErr) { 1.288 + return CUBEB_ERROR; 1.289 + } 1.290 + 1.291 + *rate = (uint32_t)fsamplerate; 1.292 + 1.293 + return CUBEB_OK; 1.294 +} 1.295 + 1.296 +static void 1.297 +audiounit_destroy(cubeb * ctx) 1.298 +{ 1.299 + int r; 1.300 + 1.301 + assert(ctx->active_streams == 0); 1.302 + 1.303 + r = pthread_mutex_destroy(&ctx->mutex); 1.304 + assert(r == 0); 1.305 + 1.306 + free(ctx); 1.307 +} 1.308 + 1.309 +static void audiounit_stream_destroy(cubeb_stream * stm); 1.310 + 1.311 +static int 1.312 +audiounit_stream_init(cubeb * context, cubeb_stream ** stream, char const * stream_name, 1.313 + cubeb_stream_params stream_params, unsigned int latency, 1.314 + cubeb_data_callback data_callback, cubeb_state_callback state_callback, 1.315 + void * user_ptr) 1.316 +{ 1.317 + AudioStreamBasicDescription ss; 1.318 +#if MAC_OS_X_VERSION_MIN_REQUIRED < 1060 1.319 + ComponentDescription desc; 1.320 + Component comp; 1.321 +#else 1.322 + AudioComponentDescription desc; 1.323 + AudioComponent comp; 1.324 +#endif 1.325 + cubeb_stream * stm; 1.326 + AURenderCallbackStruct input; 1.327 + unsigned int buffer_size, default_buffer_size; 1.328 + OSStatus r; 1.329 + UInt32 size; 1.330 + AudioDeviceID output_device_id; 1.331 + AudioValueRange latency_range; 1.332 + 1.333 + assert(context); 1.334 + *stream = NULL; 1.335 + 1.336 + memset(&ss, 0, sizeof(ss)); 1.337 + ss.mFormatFlags = 0; 1.338 + 1.339 + switch (stream_params.format) { 1.340 + case CUBEB_SAMPLE_S16LE: 1.341 + ss.mBitsPerChannel = 16; 1.342 + ss.mFormatFlags |= kAudioFormatFlagIsSignedInteger; 1.343 + break; 1.344 + case CUBEB_SAMPLE_S16BE: 1.345 + ss.mBitsPerChannel = 16; 1.346 + ss.mFormatFlags |= kAudioFormatFlagIsSignedInteger | 1.347 + kAudioFormatFlagIsBigEndian; 1.348 + break; 1.349 + case CUBEB_SAMPLE_FLOAT32LE: 1.350 + ss.mBitsPerChannel = 32; 1.351 + ss.mFormatFlags |= kAudioFormatFlagIsFloat; 1.352 + break; 1.353 + case CUBEB_SAMPLE_FLOAT32BE: 1.354 + ss.mBitsPerChannel = 32; 1.355 + ss.mFormatFlags |= kAudioFormatFlagIsFloat | 1.356 + kAudioFormatFlagIsBigEndian; 1.357 + break; 1.358 + default: 1.359 + return CUBEB_ERROR_INVALID_FORMAT; 1.360 + } 1.361 + 1.362 + ss.mFormatID = kAudioFormatLinearPCM; 1.363 + ss.mFormatFlags |= kLinearPCMFormatFlagIsPacked; 1.364 + ss.mSampleRate = stream_params.rate; 1.365 + ss.mChannelsPerFrame = stream_params.channels; 1.366 + 1.367 + ss.mBytesPerFrame = (ss.mBitsPerChannel / 8) * ss.mChannelsPerFrame; 1.368 + ss.mFramesPerPacket = 1; 1.369 + ss.mBytesPerPacket = ss.mBytesPerFrame * ss.mFramesPerPacket; 1.370 + 1.371 + pthread_mutex_lock(&context->mutex); 1.372 + if (context->limit_streams && context->active_streams >= CUBEB_STREAM_MAX) { 1.373 + pthread_mutex_unlock(&context->mutex); 1.374 + return CUBEB_ERROR; 1.375 + } 1.376 + context->active_streams += 1; 1.377 + pthread_mutex_unlock(&context->mutex); 1.378 + 1.379 + desc.componentType = kAudioUnitType_Output; 1.380 + desc.componentSubType = kAudioUnitSubType_DefaultOutput; 1.381 + desc.componentManufacturer = kAudioUnitManufacturer_Apple; 1.382 + desc.componentFlags = 0; 1.383 + desc.componentFlagsMask = 0; 1.384 +#if MAC_OS_X_VERSION_MIN_REQUIRED < 1060 1.385 + comp = FindNextComponent(NULL, &desc); 1.386 +#else 1.387 + comp = AudioComponentFindNext(NULL, &desc); 1.388 +#endif 1.389 + assert(comp); 1.390 + 1.391 + stm = calloc(1, sizeof(*stm)); 1.392 + assert(stm); 1.393 + 1.394 + stm->context = context; 1.395 + stm->data_callback = data_callback; 1.396 + stm->state_callback = state_callback; 1.397 + stm->user_ptr = user_ptr; 1.398 + 1.399 + stm->sample_spec = ss; 1.400 + 1.401 + r = pthread_mutex_init(&stm->mutex, NULL); 1.402 + assert(r == 0); 1.403 + 1.404 + stm->frames_played = 0; 1.405 + stm->frames_queued = 0; 1.406 + stm->current_latency_frames = 0; 1.407 + stm->hw_latency_frames = UINT64_MAX; 1.408 + 1.409 +#if MAC_OS_X_VERSION_MIN_REQUIRED < 1060 1.410 + r = OpenAComponent(comp, &stm->unit); 1.411 +#else 1.412 + r = AudioComponentInstanceNew(comp, &stm->unit); 1.413 +#endif 1.414 + if (r != 0) { 1.415 + audiounit_stream_destroy(stm); 1.416 + return CUBEB_ERROR; 1.417 + } 1.418 + 1.419 + input.inputProc = audiounit_output_callback; 1.420 + input.inputProcRefCon = stm; 1.421 + r = AudioUnitSetProperty(stm->unit, kAudioUnitProperty_SetRenderCallback, 1.422 + kAudioUnitScope_Global, 0, &input, sizeof(input)); 1.423 + if (r != 0) { 1.424 + audiounit_stream_destroy(stm); 1.425 + return CUBEB_ERROR; 1.426 + } 1.427 + 1.428 + buffer_size = latency / 1000.0 * ss.mSampleRate; 1.429 + 1.430 + /* Get the range of latency this particular device can work with, and clamp 1.431 + * the requested latency to this acceptable range. */ 1.432 + if (audiounit_get_acceptable_latency_range(&latency_range) != CUBEB_OK) { 1.433 + audiounit_stream_destroy(stm); 1.434 + return CUBEB_ERROR; 1.435 + } 1.436 + 1.437 + if (buffer_size < (unsigned int) latency_range.mMinimum) { 1.438 + buffer_size = (unsigned int) latency_range.mMinimum; 1.439 + } else if (buffer_size > (unsigned int) latency_range.mMaximum) { 1.440 + buffer_size = (unsigned int) latency_range.mMaximum; 1.441 + } 1.442 + 1.443 + /** 1.444 + * Get the default buffer size. If our latency request is below the default, 1.445 + * set it. Otherwise, use the default latency. 1.446 + **/ 1.447 + size = sizeof(default_buffer_size); 1.448 + r = AudioUnitGetProperty(stm->unit, kAudioDevicePropertyBufferFrameSize, 1.449 + kAudioUnitScope_Output, 0, &default_buffer_size, &size); 1.450 + 1.451 + if (r != 0) { 1.452 + audiounit_stream_destroy(stm); 1.453 + return CUBEB_ERROR; 1.454 + } 1.455 + 1.456 + // Setting the latency doesn't work well for USB headsets (eg. plantronics). 1.457 + // Keep the default latency for now. 1.458 +#if 0 1.459 + if (buffer_size < default_buffer_size) { 1.460 + /* Set the maximum number of frame that the render callback will ask for, 1.461 + * effectively setting the latency of the stream. This is process-wide. */ 1.462 + r = AudioUnitSetProperty(stm->unit, kAudioDevicePropertyBufferFrameSize, 1.463 + kAudioUnitScope_Output, 0, &buffer_size, sizeof(buffer_size)); 1.464 + if (r != 0) { 1.465 + audiounit_stream_destroy(stm); 1.466 + return CUBEB_ERROR; 1.467 + } 1.468 + } 1.469 +#endif 1.470 + 1.471 + r = AudioUnitSetProperty(stm->unit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 1.472 + 0, &ss, sizeof(ss)); 1.473 + if (r != 0) { 1.474 + audiounit_stream_destroy(stm); 1.475 + return CUBEB_ERROR; 1.476 + } 1.477 + 1.478 + r = AudioUnitInitialize(stm->unit); 1.479 + if (r != 0) { 1.480 + audiounit_stream_destroy(stm); 1.481 + return CUBEB_ERROR; 1.482 + } 1.483 + 1.484 + *stream = stm; 1.485 + 1.486 + return CUBEB_OK; 1.487 +} 1.488 + 1.489 +static void 1.490 +audiounit_stream_destroy(cubeb_stream * stm) 1.491 +{ 1.492 + int r; 1.493 + 1.494 + stm->shutdown = 1; 1.495 + 1.496 + if (stm->unit) { 1.497 + AudioOutputUnitStop(stm->unit); 1.498 + AudioUnitUninitialize(stm->unit); 1.499 +#if MAC_OS_X_VERSION_MIN_REQUIRED < 1060 1.500 + CloseComponent(stm->unit); 1.501 +#else 1.502 + AudioComponentInstanceDispose(stm->unit); 1.503 +#endif 1.504 + } 1.505 + 1.506 + r = pthread_mutex_destroy(&stm->mutex); 1.507 + assert(r == 0); 1.508 + 1.509 + pthread_mutex_lock(&stm->context->mutex); 1.510 + assert(stm->context->active_streams >= 1); 1.511 + stm->context->active_streams -= 1; 1.512 + pthread_mutex_unlock(&stm->context->mutex); 1.513 + 1.514 + free(stm); 1.515 +} 1.516 + 1.517 +static int 1.518 +audiounit_stream_start(cubeb_stream * stm) 1.519 +{ 1.520 + OSStatus r; 1.521 + r = AudioOutputUnitStart(stm->unit); 1.522 + assert(r == 0); 1.523 + stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STARTED); 1.524 + return CUBEB_OK; 1.525 +} 1.526 + 1.527 +static int 1.528 +audiounit_stream_stop(cubeb_stream * stm) 1.529 +{ 1.530 + OSStatus r; 1.531 + r = AudioOutputUnitStop(stm->unit); 1.532 + assert(r == 0); 1.533 + stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STOPPED); 1.534 + return CUBEB_OK; 1.535 +} 1.536 + 1.537 +static int 1.538 +audiounit_stream_get_position(cubeb_stream * stm, uint64_t * position) 1.539 +{ 1.540 + pthread_mutex_lock(&stm->mutex); 1.541 + *position = stm->frames_played; 1.542 + pthread_mutex_unlock(&stm->mutex); 1.543 + return CUBEB_OK; 1.544 +} 1.545 + 1.546 +int 1.547 +audiounit_stream_get_latency(cubeb_stream * stm, uint32_t * latency) 1.548 +{ 1.549 + pthread_mutex_lock(&stm->mutex); 1.550 + if (stm->hw_latency_frames == UINT64_MAX) { 1.551 + UInt32 size; 1.552 + uint32_t device_latency_frames, device_safety_offset; 1.553 + double unit_latency_sec; 1.554 + AudioDeviceID output_device_id; 1.555 + OSStatus r; 1.556 + AudioObjectPropertyAddress latency_address = { 1.557 + kAudioDevicePropertyLatency, 1.558 + kAudioDevicePropertyScopeOutput, 1.559 + kAudioObjectPropertyElementMaster 1.560 + }; 1.561 + AudioObjectPropertyAddress safety_offset_address = { 1.562 + kAudioDevicePropertySafetyOffset, 1.563 + kAudioDevicePropertyScopeOutput, 1.564 + kAudioObjectPropertyElementMaster 1.565 + }; 1.566 + 1.567 + r = audiounit_get_output_device_id(&output_device_id); 1.568 + 1.569 + if (r != noErr) { 1.570 + pthread_mutex_unlock(&stm->mutex); 1.571 + return CUBEB_ERROR; 1.572 + } 1.573 + 1.574 + size = sizeof(unit_latency_sec); 1.575 + r = AudioUnitGetProperty(stm->unit, 1.576 + kAudioUnitProperty_Latency, 1.577 + kAudioUnitScope_Global, 1.578 + 0, 1.579 + &unit_latency_sec, 1.580 + &size); 1.581 + if (r != noErr) { 1.582 + pthread_mutex_unlock(&stm->mutex); 1.583 + return CUBEB_ERROR; 1.584 + } 1.585 + 1.586 + size = sizeof(device_latency_frames); 1.587 + r = AudioObjectGetPropertyData(output_device_id, 1.588 + &latency_address, 1.589 + 0, 1.590 + NULL, 1.591 + &size, 1.592 + &device_latency_frames); 1.593 + if (r != noErr) { 1.594 + pthread_mutex_unlock(&stm->mutex); 1.595 + return CUBEB_ERROR; 1.596 + } 1.597 + 1.598 + size = sizeof(device_safety_offset); 1.599 + r = AudioObjectGetPropertyData(output_device_id, 1.600 + &safety_offset_address, 1.601 + 0, 1.602 + NULL, 1.603 + &size, 1.604 + &device_safety_offset); 1.605 + if (r != noErr) { 1.606 + pthread_mutex_unlock(&stm->mutex); 1.607 + return CUBEB_ERROR; 1.608 + } 1.609 + 1.610 + /* This part is fixed and depend on the stream parameter and the hardware. */ 1.611 + stm->hw_latency_frames = 1.612 + (uint32_t)(unit_latency_sec * stm->sample_spec.mSampleRate) 1.613 + + device_latency_frames 1.614 + + device_safety_offset; 1.615 + } 1.616 + 1.617 + *latency = stm->hw_latency_frames + stm->current_latency_frames; 1.618 + pthread_mutex_unlock(&stm->mutex); 1.619 + 1.620 + return CUBEB_OK; 1.621 +} 1.622 + 1.623 +static struct cubeb_ops const audiounit_ops = { 1.624 + .init = audiounit_init, 1.625 + .get_backend_id = audiounit_get_backend_id, 1.626 + .get_max_channel_count = audiounit_get_max_channel_count, 1.627 + .get_min_latency = audiounit_get_min_latency, 1.628 + .get_preferred_sample_rate = audiounit_get_preferred_sample_rate, 1.629 + .destroy = audiounit_destroy, 1.630 + .stream_init = audiounit_stream_init, 1.631 + .stream_destroy = audiounit_stream_destroy, 1.632 + .stream_start = audiounit_stream_start, 1.633 + .stream_stop = audiounit_stream_stop, 1.634 + .stream_get_position = audiounit_stream_get_position, 1.635 + .stream_get_latency = audiounit_stream_get_latency 1.636 +};