michael@0: /* michael@0: * Copyright © 2011 Mozilla Foundation michael@0: * michael@0: * This program is made available under an ISC-style license. See the michael@0: * accompanying file LICENSE for details. michael@0: */ michael@0: #undef NDEBUG michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: #include "cubeb/cubeb.h" michael@0: #include "cubeb-internal.h" michael@0: michael@0: #if !defined(kCFCoreFoundationVersionNumber10_7) michael@0: /* From CoreFoundation CFBase.h */ michael@0: #define kCFCoreFoundationVersionNumber10_7 635.00 michael@0: #endif michael@0: michael@0: #define CUBEB_STREAM_MAX 16 michael@0: #define NBUFS 4 michael@0: michael@0: static struct cubeb_ops const audiounit_ops; michael@0: michael@0: struct cubeb { michael@0: struct cubeb_ops const * ops; michael@0: pthread_mutex_t mutex; michael@0: int active_streams; michael@0: int limit_streams; michael@0: }; michael@0: michael@0: struct cubeb_stream { michael@0: cubeb * context; michael@0: AudioUnit unit; michael@0: cubeb_data_callback data_callback; michael@0: cubeb_state_callback state_callback; michael@0: void * user_ptr; michael@0: AudioStreamBasicDescription sample_spec; michael@0: pthread_mutex_t mutex; michael@0: uint64_t frames_played; michael@0: uint64_t frames_queued; michael@0: int shutdown; michael@0: int draining; michael@0: uint64_t current_latency_frames; michael@0: uint64_t hw_latency_frames; michael@0: }; michael@0: michael@0: static int64_t michael@0: audiotimestamp_to_latency(AudioTimeStamp const * tstamp, cubeb_stream * stream) michael@0: { michael@0: if (!(tstamp->mFlags & kAudioTimeStampHostTimeValid)) { michael@0: return 0; michael@0: } michael@0: michael@0: uint64_t pres = AudioConvertHostTimeToNanos(tstamp->mHostTime); michael@0: uint64_t now = AudioConvertHostTimeToNanos(AudioGetCurrentHostTime()); michael@0: michael@0: return ((pres - now) * stream->sample_spec.mSampleRate) / 1000000000LL; michael@0: } michael@0: michael@0: static OSStatus michael@0: audiounit_output_callback(void * user_ptr, AudioUnitRenderActionFlags * flags, michael@0: AudioTimeStamp const * tstamp, UInt32 bus, UInt32 nframes, michael@0: AudioBufferList * bufs) michael@0: { michael@0: cubeb_stream * stm; michael@0: unsigned char * buf; michael@0: long got; michael@0: OSStatus r; michael@0: michael@0: assert(bufs->mNumberBuffers == 1); michael@0: buf = bufs->mBuffers[0].mData; michael@0: michael@0: stm = user_ptr; michael@0: michael@0: pthread_mutex_lock(&stm->mutex); michael@0: michael@0: stm->current_latency_frames = audiotimestamp_to_latency(tstamp, stm); michael@0: michael@0: if (stm->draining || stm->shutdown) { michael@0: pthread_mutex_unlock(&stm->mutex); michael@0: if (stm->draining) { michael@0: r = AudioOutputUnitStop(stm->unit); michael@0: assert(r == 0); michael@0: stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_DRAINED); michael@0: } michael@0: return noErr; michael@0: } michael@0: michael@0: pthread_mutex_unlock(&stm->mutex); michael@0: got = stm->data_callback(stm, stm->user_ptr, buf, nframes); michael@0: pthread_mutex_lock(&stm->mutex); michael@0: if (got < 0) { michael@0: /* XXX handle this case. */ michael@0: assert(false); michael@0: pthread_mutex_unlock(&stm->mutex); michael@0: return noErr; michael@0: } michael@0: michael@0: if ((UInt32) got < nframes) { michael@0: size_t got_bytes = got * stm->sample_spec.mBytesPerFrame; michael@0: size_t rem_bytes = (nframes - got) * stm->sample_spec.mBytesPerFrame; michael@0: michael@0: stm->draining = 1; michael@0: michael@0: memset(buf + got_bytes, 0, rem_bytes); michael@0: } michael@0: michael@0: stm->frames_played = stm->frames_queued; michael@0: stm->frames_queued += got; michael@0: pthread_mutex_unlock(&stm->mutex); michael@0: michael@0: return noErr; michael@0: } michael@0: michael@0: /*static*/ int michael@0: audiounit_init(cubeb ** context, char const * context_name) michael@0: { michael@0: cubeb * ctx; michael@0: int r; michael@0: michael@0: *context = NULL; michael@0: michael@0: ctx = calloc(1, sizeof(*ctx)); michael@0: assert(ctx); michael@0: michael@0: ctx->ops = &audiounit_ops; michael@0: michael@0: r = pthread_mutex_init(&ctx->mutex, NULL); michael@0: assert(r == 0); michael@0: michael@0: ctx->active_streams = 0; michael@0: michael@0: ctx->limit_streams = kCFCoreFoundationVersionNumber < kCFCoreFoundationVersionNumber10_7; michael@0: michael@0: *context = ctx; michael@0: michael@0: return CUBEB_OK; michael@0: } michael@0: michael@0: static char const * michael@0: audiounit_get_backend_id(cubeb * ctx) michael@0: { michael@0: return "audiounit"; michael@0: } michael@0: michael@0: static int michael@0: audiounit_get_output_device_id(AudioDeviceID * device_id) michael@0: { michael@0: UInt32 size; michael@0: OSStatus r; michael@0: AudioObjectPropertyAddress output_device_address = { michael@0: kAudioHardwarePropertyDefaultOutputDevice, michael@0: kAudioObjectPropertyScopeGlobal, michael@0: kAudioObjectPropertyElementMaster michael@0: }; michael@0: michael@0: size = sizeof(*device_id); michael@0: michael@0: r = AudioObjectGetPropertyData(kAudioObjectSystemObject, michael@0: &output_device_address, michael@0: 0, michael@0: NULL, michael@0: &size, michael@0: device_id); michael@0: if (r != noErr) { michael@0: return CUBEB_ERROR; michael@0: } michael@0: michael@0: return CUBEB_OK; michael@0: } michael@0: michael@0: /* Get the acceptable buffer size (in frames) that this device can work with. */ michael@0: static int michael@0: audiounit_get_acceptable_latency_range(AudioValueRange * latency_range) michael@0: { michael@0: UInt32 size; michael@0: OSStatus r; michael@0: AudioDeviceID output_device_id; michael@0: AudioObjectPropertyAddress output_device_buffer_size_range = { michael@0: kAudioDevicePropertyBufferFrameSizeRange, michael@0: kAudioDevicePropertyScopeOutput, michael@0: kAudioObjectPropertyElementMaster michael@0: }; michael@0: michael@0: if (audiounit_get_output_device_id(&output_device_id) != CUBEB_OK) { michael@0: return CUBEB_ERROR; michael@0: } michael@0: michael@0: /* Get the buffer size range this device supports */ michael@0: size = sizeof(*latency_range); michael@0: michael@0: r = AudioObjectGetPropertyData(output_device_id, michael@0: &output_device_buffer_size_range, michael@0: 0, michael@0: NULL, michael@0: &size, michael@0: latency_range); michael@0: if (r != noErr) { michael@0: return CUBEB_ERROR; michael@0: } michael@0: michael@0: return CUBEB_OK; michael@0: } michael@0: michael@0: int michael@0: audiounit_get_max_channel_count(cubeb * ctx, uint32_t * max_channels) michael@0: { michael@0: UInt32 size; michael@0: OSStatus r; michael@0: AudioDeviceID output_device_id; michael@0: AudioStreamBasicDescription stream_format; michael@0: AudioObjectPropertyAddress stream_format_address = { michael@0: kAudioDevicePropertyStreamFormat, michael@0: kAudioDevicePropertyScopeOutput, michael@0: kAudioObjectPropertyElementMaster michael@0: }; michael@0: michael@0: assert(ctx && max_channels); michael@0: michael@0: if (audiounit_get_output_device_id(&output_device_id) != CUBEB_OK) { michael@0: return CUBEB_ERROR; michael@0: } michael@0: michael@0: size = sizeof(stream_format); michael@0: michael@0: r = AudioObjectGetPropertyData(output_device_id, michael@0: &stream_format_address, michael@0: 0, michael@0: NULL, michael@0: &size, michael@0: &stream_format); michael@0: if (r != noErr) { michael@0: return CUBEB_ERROR; michael@0: } michael@0: michael@0: *max_channels = stream_format.mChannelsPerFrame; michael@0: michael@0: return CUBEB_OK; michael@0: } michael@0: michael@0: static int michael@0: audiounit_get_min_latency(cubeb * ctx, cubeb_stream_params params, uint32_t * latency_ms) michael@0: { michael@0: AudioValueRange latency_range; michael@0: michael@0: if (audiounit_get_acceptable_latency_range(&latency_range) != CUBEB_OK) { michael@0: return CUBEB_ERROR; michael@0: } michael@0: michael@0: *latency_ms = (latency_range.mMinimum * 1000 + params.rate - 1) / params.rate; michael@0: michael@0: return CUBEB_OK; michael@0: } michael@0: michael@0: static int michael@0: audiounit_get_preferred_sample_rate(cubeb * ctx, uint32_t * rate) michael@0: { michael@0: UInt32 size; michael@0: OSStatus r; michael@0: Float64 fsamplerate; michael@0: AudioDeviceID output_device_id; michael@0: AudioObjectPropertyAddress samplerate_address = { michael@0: kAudioDevicePropertyNominalSampleRate, michael@0: kAudioObjectPropertyScopeGlobal, michael@0: kAudioObjectPropertyElementMaster michael@0: }; michael@0: michael@0: if (audiounit_get_output_device_id(&output_device_id) != CUBEB_OK) { michael@0: return CUBEB_ERROR; michael@0: } michael@0: michael@0: size = sizeof(fsamplerate); michael@0: r = AudioObjectGetPropertyData(output_device_id, michael@0: &samplerate_address, michael@0: 0, michael@0: NULL, michael@0: &size, michael@0: &fsamplerate); michael@0: michael@0: if (r != noErr) { michael@0: return CUBEB_ERROR; michael@0: } michael@0: michael@0: *rate = (uint32_t)fsamplerate; michael@0: michael@0: return CUBEB_OK; michael@0: } michael@0: michael@0: static void michael@0: audiounit_destroy(cubeb * ctx) michael@0: { michael@0: int r; michael@0: michael@0: assert(ctx->active_streams == 0); michael@0: michael@0: r = pthread_mutex_destroy(&ctx->mutex); michael@0: assert(r == 0); michael@0: michael@0: free(ctx); michael@0: } michael@0: michael@0: static void audiounit_stream_destroy(cubeb_stream * stm); michael@0: michael@0: static int michael@0: audiounit_stream_init(cubeb * context, cubeb_stream ** stream, char const * stream_name, michael@0: cubeb_stream_params stream_params, unsigned int latency, michael@0: cubeb_data_callback data_callback, cubeb_state_callback state_callback, michael@0: void * user_ptr) michael@0: { michael@0: AudioStreamBasicDescription ss; michael@0: #if MAC_OS_X_VERSION_MIN_REQUIRED < 1060 michael@0: ComponentDescription desc; michael@0: Component comp; michael@0: #else michael@0: AudioComponentDescription desc; michael@0: AudioComponent comp; michael@0: #endif michael@0: cubeb_stream * stm; michael@0: AURenderCallbackStruct input; michael@0: unsigned int buffer_size, default_buffer_size; michael@0: OSStatus r; michael@0: UInt32 size; michael@0: AudioDeviceID output_device_id; michael@0: AudioValueRange latency_range; michael@0: michael@0: assert(context); michael@0: *stream = NULL; michael@0: michael@0: memset(&ss, 0, sizeof(ss)); michael@0: ss.mFormatFlags = 0; michael@0: michael@0: switch (stream_params.format) { michael@0: case CUBEB_SAMPLE_S16LE: michael@0: ss.mBitsPerChannel = 16; michael@0: ss.mFormatFlags |= kAudioFormatFlagIsSignedInteger; michael@0: break; michael@0: case CUBEB_SAMPLE_S16BE: michael@0: ss.mBitsPerChannel = 16; michael@0: ss.mFormatFlags |= kAudioFormatFlagIsSignedInteger | michael@0: kAudioFormatFlagIsBigEndian; michael@0: break; michael@0: case CUBEB_SAMPLE_FLOAT32LE: michael@0: ss.mBitsPerChannel = 32; michael@0: ss.mFormatFlags |= kAudioFormatFlagIsFloat; michael@0: break; michael@0: case CUBEB_SAMPLE_FLOAT32BE: michael@0: ss.mBitsPerChannel = 32; michael@0: ss.mFormatFlags |= kAudioFormatFlagIsFloat | michael@0: kAudioFormatFlagIsBigEndian; michael@0: break; michael@0: default: michael@0: return CUBEB_ERROR_INVALID_FORMAT; michael@0: } michael@0: michael@0: ss.mFormatID = kAudioFormatLinearPCM; michael@0: ss.mFormatFlags |= kLinearPCMFormatFlagIsPacked; michael@0: ss.mSampleRate = stream_params.rate; michael@0: ss.mChannelsPerFrame = stream_params.channels; michael@0: michael@0: ss.mBytesPerFrame = (ss.mBitsPerChannel / 8) * ss.mChannelsPerFrame; michael@0: ss.mFramesPerPacket = 1; michael@0: ss.mBytesPerPacket = ss.mBytesPerFrame * ss.mFramesPerPacket; michael@0: michael@0: pthread_mutex_lock(&context->mutex); michael@0: if (context->limit_streams && context->active_streams >= CUBEB_STREAM_MAX) { michael@0: pthread_mutex_unlock(&context->mutex); michael@0: return CUBEB_ERROR; michael@0: } michael@0: context->active_streams += 1; michael@0: pthread_mutex_unlock(&context->mutex); michael@0: michael@0: desc.componentType = kAudioUnitType_Output; michael@0: desc.componentSubType = kAudioUnitSubType_DefaultOutput; michael@0: desc.componentManufacturer = kAudioUnitManufacturer_Apple; michael@0: desc.componentFlags = 0; michael@0: desc.componentFlagsMask = 0; michael@0: #if MAC_OS_X_VERSION_MIN_REQUIRED < 1060 michael@0: comp = FindNextComponent(NULL, &desc); michael@0: #else michael@0: comp = AudioComponentFindNext(NULL, &desc); michael@0: #endif michael@0: assert(comp); michael@0: michael@0: stm = calloc(1, sizeof(*stm)); michael@0: assert(stm); michael@0: michael@0: stm->context = context; michael@0: stm->data_callback = data_callback; michael@0: stm->state_callback = state_callback; michael@0: stm->user_ptr = user_ptr; michael@0: michael@0: stm->sample_spec = ss; michael@0: michael@0: r = pthread_mutex_init(&stm->mutex, NULL); michael@0: assert(r == 0); michael@0: michael@0: stm->frames_played = 0; michael@0: stm->frames_queued = 0; michael@0: stm->current_latency_frames = 0; michael@0: stm->hw_latency_frames = UINT64_MAX; michael@0: michael@0: #if MAC_OS_X_VERSION_MIN_REQUIRED < 1060 michael@0: r = OpenAComponent(comp, &stm->unit); michael@0: #else michael@0: r = AudioComponentInstanceNew(comp, &stm->unit); michael@0: #endif michael@0: if (r != 0) { michael@0: audiounit_stream_destroy(stm); michael@0: return CUBEB_ERROR; michael@0: } michael@0: michael@0: input.inputProc = audiounit_output_callback; michael@0: input.inputProcRefCon = stm; michael@0: r = AudioUnitSetProperty(stm->unit, kAudioUnitProperty_SetRenderCallback, michael@0: kAudioUnitScope_Global, 0, &input, sizeof(input)); michael@0: if (r != 0) { michael@0: audiounit_stream_destroy(stm); michael@0: return CUBEB_ERROR; michael@0: } michael@0: michael@0: buffer_size = latency / 1000.0 * ss.mSampleRate; michael@0: michael@0: /* Get the range of latency this particular device can work with, and clamp michael@0: * the requested latency to this acceptable range. */ michael@0: if (audiounit_get_acceptable_latency_range(&latency_range) != CUBEB_OK) { michael@0: audiounit_stream_destroy(stm); michael@0: return CUBEB_ERROR; michael@0: } michael@0: michael@0: if (buffer_size < (unsigned int) latency_range.mMinimum) { michael@0: buffer_size = (unsigned int) latency_range.mMinimum; michael@0: } else if (buffer_size > (unsigned int) latency_range.mMaximum) { michael@0: buffer_size = (unsigned int) latency_range.mMaximum; michael@0: } michael@0: michael@0: /** michael@0: * Get the default buffer size. If our latency request is below the default, michael@0: * set it. Otherwise, use the default latency. michael@0: **/ michael@0: size = sizeof(default_buffer_size); michael@0: r = AudioUnitGetProperty(stm->unit, kAudioDevicePropertyBufferFrameSize, michael@0: kAudioUnitScope_Output, 0, &default_buffer_size, &size); michael@0: michael@0: if (r != 0) { michael@0: audiounit_stream_destroy(stm); michael@0: return CUBEB_ERROR; michael@0: } michael@0: michael@0: // Setting the latency doesn't work well for USB headsets (eg. plantronics). michael@0: // Keep the default latency for now. michael@0: #if 0 michael@0: if (buffer_size < default_buffer_size) { michael@0: /* Set the maximum number of frame that the render callback will ask for, michael@0: * effectively setting the latency of the stream. This is process-wide. */ michael@0: r = AudioUnitSetProperty(stm->unit, kAudioDevicePropertyBufferFrameSize, michael@0: kAudioUnitScope_Output, 0, &buffer_size, sizeof(buffer_size)); michael@0: if (r != 0) { michael@0: audiounit_stream_destroy(stm); michael@0: return CUBEB_ERROR; michael@0: } michael@0: } michael@0: #endif michael@0: michael@0: r = AudioUnitSetProperty(stm->unit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, michael@0: 0, &ss, sizeof(ss)); michael@0: if (r != 0) { michael@0: audiounit_stream_destroy(stm); michael@0: return CUBEB_ERROR; michael@0: } michael@0: michael@0: r = AudioUnitInitialize(stm->unit); michael@0: if (r != 0) { michael@0: audiounit_stream_destroy(stm); michael@0: return CUBEB_ERROR; michael@0: } michael@0: michael@0: *stream = stm; michael@0: michael@0: return CUBEB_OK; michael@0: } michael@0: michael@0: static void michael@0: audiounit_stream_destroy(cubeb_stream * stm) michael@0: { michael@0: int r; michael@0: michael@0: stm->shutdown = 1; michael@0: michael@0: if (stm->unit) { michael@0: AudioOutputUnitStop(stm->unit); michael@0: AudioUnitUninitialize(stm->unit); michael@0: #if MAC_OS_X_VERSION_MIN_REQUIRED < 1060 michael@0: CloseComponent(stm->unit); michael@0: #else michael@0: AudioComponentInstanceDispose(stm->unit); michael@0: #endif michael@0: } michael@0: michael@0: r = pthread_mutex_destroy(&stm->mutex); michael@0: assert(r == 0); michael@0: michael@0: pthread_mutex_lock(&stm->context->mutex); michael@0: assert(stm->context->active_streams >= 1); michael@0: stm->context->active_streams -= 1; michael@0: pthread_mutex_unlock(&stm->context->mutex); michael@0: michael@0: free(stm); michael@0: } michael@0: michael@0: static int michael@0: audiounit_stream_start(cubeb_stream * stm) michael@0: { michael@0: OSStatus r; michael@0: r = AudioOutputUnitStart(stm->unit); michael@0: assert(r == 0); michael@0: stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STARTED); michael@0: return CUBEB_OK; michael@0: } michael@0: michael@0: static int michael@0: audiounit_stream_stop(cubeb_stream * stm) michael@0: { michael@0: OSStatus r; michael@0: r = AudioOutputUnitStop(stm->unit); michael@0: assert(r == 0); michael@0: stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STOPPED); michael@0: return CUBEB_OK; michael@0: } michael@0: michael@0: static int michael@0: audiounit_stream_get_position(cubeb_stream * stm, uint64_t * position) michael@0: { michael@0: pthread_mutex_lock(&stm->mutex); michael@0: *position = stm->frames_played; michael@0: pthread_mutex_unlock(&stm->mutex); michael@0: return CUBEB_OK; michael@0: } michael@0: michael@0: int michael@0: audiounit_stream_get_latency(cubeb_stream * stm, uint32_t * latency) michael@0: { michael@0: pthread_mutex_lock(&stm->mutex); michael@0: if (stm->hw_latency_frames == UINT64_MAX) { michael@0: UInt32 size; michael@0: uint32_t device_latency_frames, device_safety_offset; michael@0: double unit_latency_sec; michael@0: AudioDeviceID output_device_id; michael@0: OSStatus r; michael@0: AudioObjectPropertyAddress latency_address = { michael@0: kAudioDevicePropertyLatency, michael@0: kAudioDevicePropertyScopeOutput, michael@0: kAudioObjectPropertyElementMaster michael@0: }; michael@0: AudioObjectPropertyAddress safety_offset_address = { michael@0: kAudioDevicePropertySafetyOffset, michael@0: kAudioDevicePropertyScopeOutput, michael@0: kAudioObjectPropertyElementMaster michael@0: }; michael@0: michael@0: r = audiounit_get_output_device_id(&output_device_id); michael@0: michael@0: if (r != noErr) { michael@0: pthread_mutex_unlock(&stm->mutex); michael@0: return CUBEB_ERROR; michael@0: } michael@0: michael@0: size = sizeof(unit_latency_sec); michael@0: r = AudioUnitGetProperty(stm->unit, michael@0: kAudioUnitProperty_Latency, michael@0: kAudioUnitScope_Global, michael@0: 0, michael@0: &unit_latency_sec, michael@0: &size); michael@0: if (r != noErr) { michael@0: pthread_mutex_unlock(&stm->mutex); michael@0: return CUBEB_ERROR; michael@0: } michael@0: michael@0: size = sizeof(device_latency_frames); michael@0: r = AudioObjectGetPropertyData(output_device_id, michael@0: &latency_address, michael@0: 0, michael@0: NULL, michael@0: &size, michael@0: &device_latency_frames); michael@0: if (r != noErr) { michael@0: pthread_mutex_unlock(&stm->mutex); michael@0: return CUBEB_ERROR; michael@0: } michael@0: michael@0: size = sizeof(device_safety_offset); michael@0: r = AudioObjectGetPropertyData(output_device_id, michael@0: &safety_offset_address, michael@0: 0, michael@0: NULL, michael@0: &size, michael@0: &device_safety_offset); michael@0: if (r != noErr) { michael@0: pthread_mutex_unlock(&stm->mutex); michael@0: return CUBEB_ERROR; michael@0: } michael@0: michael@0: /* This part is fixed and depend on the stream parameter and the hardware. */ michael@0: stm->hw_latency_frames = michael@0: (uint32_t)(unit_latency_sec * stm->sample_spec.mSampleRate) michael@0: + device_latency_frames michael@0: + device_safety_offset; michael@0: } michael@0: michael@0: *latency = stm->hw_latency_frames + stm->current_latency_frames; michael@0: pthread_mutex_unlock(&stm->mutex); michael@0: michael@0: return CUBEB_OK; michael@0: } michael@0: michael@0: static struct cubeb_ops const audiounit_ops = { michael@0: .init = audiounit_init, michael@0: .get_backend_id = audiounit_get_backend_id, michael@0: .get_max_channel_count = audiounit_get_max_channel_count, michael@0: .get_min_latency = audiounit_get_min_latency, michael@0: .get_preferred_sample_rate = audiounit_get_preferred_sample_rate, michael@0: .destroy = audiounit_destroy, michael@0: .stream_init = audiounit_stream_init, michael@0: .stream_destroy = audiounit_stream_destroy, michael@0: .stream_start = audiounit_stream_start, michael@0: .stream_stop = audiounit_stream_stop, michael@0: .stream_get_position = audiounit_stream_get_position, michael@0: .stream_get_latency = audiounit_stream_get_latency michael@0: };