media/libcubeb/src/cubeb_audiounit.c

Thu, 22 Jan 2015 13:21:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 22 Jan 2015 13:21:57 +0100
branch
TOR_BUG_9701
changeset 15
b8a032363ba2
permissions
-rw-r--r--

Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6

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

mercurial