static AudioChannelLayout* ca_query_layout(struct ao *ao, AudioDeviceID device, void *talloc_ctx) { OSStatus err; uint32_t psize; AudioChannelLayout *r = NULL; AudioObjectPropertyAddress p_addr = (AudioObjectPropertyAddress) { .mSelector = kAudioDevicePropertyPreferredChannelLayout, .mScope = kAudioDevicePropertyScopeOutput, .mElement = kAudioObjectPropertyElementWildcard, }; err = AudioObjectGetPropertyDataSize(device, &p_addr, 0, NULL, &psize); CHECK_CA_ERROR("could not get device preferred layout (size)"); r = talloc_size(talloc_ctx, psize); err = AudioObjectGetPropertyData(device, &p_addr, 0, NULL, &psize, r); CHECK_CA_ERROR("could not get device preferred layout (get)"); coreaudio_error: return r; }
OSStatus ca_select_device(struct ao *ao, char* name, AudioDeviceID *device) { OSStatus err = noErr; int selection = name ? strtol(name, (char **)NULL, 10) : -1; if (errno == EINVAL || errno == ERANGE) { selection = -1; MP_WARN(ao, "device identifier '%s' is invalid\n", name); } *device = 0; if (selection < 0) { // device not set by user, get the default one err = CA_GET(kAudioObjectSystemObject, kAudioHardwarePropertyDefaultOutputDevice, device); CHECK_CA_ERROR("could not get default audio device"); } else { *device = selection; } if (mp_msg_test(ao->log, MSGL_V)) { char *desc; err = CA_GET_STR(*device, kAudioObjectPropertyName, &desc); CHECK_CA_ERROR("could not get selected audio device name"); MP_VERBOSE(ao, "selected audio output device: %s (%" PRIu32 ")\n", desc, *device); talloc_free(desc); } coreaudio_error: return err; }
static int64_t ca_get_hardware_latency(struct ao *ao) { struct priv *p = ao->priv; double audiounit_latency_sec = 0.0; uint32_t size = sizeof(audiounit_latency_sec); OSStatus err = AudioUnitGetProperty( p->audio_unit, kAudioUnitProperty_Latency, kAudioUnitScope_Global, 0, &audiounit_latency_sec, &size); CHECK_CA_ERROR("cannot get audio unit latency"); uint32_t frames = 0; err = CA_GET_O(p->device, kAudioDevicePropertyLatency, &frames); CHECK_CA_ERROR("cannot get device latency"); uint64_t audiounit_latency_us = audiounit_latency_sec * 1e6; uint64_t device_latency_us = ca_frames_to_us(ao, frames); MP_VERBOSE(ao, "audiounit latency [us]: %lld\n", audiounit_latency_us); MP_VERBOSE(ao, "device latency [us]: %lld\n", device_latency_us); return audiounit_latency_us + device_latency_us; coreaudio_error: return 0; }
static AudioChannelLayout* ca_query_stereo_layout(struct ao *ao, AudioDeviceID device, void *talloc_ctx) { OSStatus err; const int nch = 2; uint32_t channels[nch]; AudioChannelLayout *r = NULL; AudioObjectPropertyAddress p_addr = (AudioObjectPropertyAddress) { .mSelector = kAudioDevicePropertyPreferredChannelsForStereo, .mScope = kAudioDevicePropertyScopeOutput, .mElement = kAudioObjectPropertyElementWildcard, }; uint32_t psize = sizeof(channels); err = AudioObjectGetPropertyData(device, &p_addr, 0, NULL, &psize, channels); CHECK_CA_ERROR("could not get device preferred stereo layout"); psize = sizeof(AudioChannelLayout) + nch * sizeof(AudioChannelDescription); r = talloc_zero_size(talloc_ctx, psize); r->mChannelLayoutTag = kAudioChannelLayoutTag_UseChannelDescriptions; r->mNumberChannelDescriptions = nch; AudioChannelDescription desc = {0}; desc.mChannelFlags = kAudioChannelFlags_AllOff; for(int i = 0; i < nch; i++) { desc.mChannelLabel = channels[i]; r->mChannelDescriptions[i] = desc; } coreaudio_error: return r; }
void ca_get_device_list(struct ao *ao, struct ao_device_list *list) { AudioDeviceID *devs; size_t n_devs; OSStatus err = CA_GET_ARY(kAudioObjectSystemObject, kAudioHardwarePropertyDevices, &devs, &n_devs); CHECK_CA_ERROR("Failed to get list of output devices."); for (int i = 0; i < n_devs; i++) { if (!ca_is_output_device(ao, devs[i])) continue; void *ta_ctx = talloc_new(NULL); char *name; char *desc; err = CA_GET_STR(devs[i], kAudioDevicePropertyDeviceUID, &name); if (err != noErr) { MP_VERBOSE(ao, "skipping device %d, which has no UID\n", i); talloc_free(ta_ctx); continue; } talloc_steal(ta_ctx, name); err = CA_GET_STR(devs[i], kAudioObjectPropertyName, &desc); if (err != noErr) desc = talloc_strdup(NULL, "Unknown"); talloc_steal(ta_ctx, desc); ao_device_list_add(list, ao, &(struct ao_device_desc){name, desc}); talloc_free(ta_ctx); }
static bool reinit_device(struct ao *ao) { struct priv *p = ao->priv; OSStatus err = ca_select_device(ao, ao->device, &p->device); CHECK_CA_ERROR("failed to select device"); char *uid; err = CA_GET_STR(p->device, kAudioDevicePropertyDeviceUID, &uid); CHECK_CA_ERROR("failed to get device UID"); ao->detected_device = talloc_steal(ao, uid); return true; coreaudio_error: return false; }
static void init_physical_format(struct ao *ao) { struct priv *p = ao->priv; OSErr err; AudioStreamBasicDescription asbd; ca_fill_asbd(ao, &asbd); AudioStreamID *streams; size_t n_streams; err = CA_GET_ARY_O(p->device, kAudioDevicePropertyStreams, &streams, &n_streams); CHECK_CA_ERROR("could not get number of streams"); for (int i = 0; i < n_streams; i++) { AudioStreamRangedDescription *formats; size_t n_formats; err = CA_GET_ARY(streams[i], kAudioStreamPropertyAvailablePhysicalFormats, &formats, &n_formats); if (!CHECK_CA_WARN("could not get number of stream formats")) continue; // try next one MP_VERBOSE(ao, "Looking at formats in substream %d...\n", i); AudioStreamBasicDescription best_asbd = {0}; for (int j = 0; j < n_formats; j++) { AudioStreamBasicDescription *stream_asbd = &formats[j].mFormat; ca_print_asbd(ao, "- ", stream_asbd); if (!best_asbd.mFormatID || ca_asbd_is_better(&asbd, &best_asbd, stream_asbd)) best_asbd = *stream_asbd; } if (best_asbd.mFormatID) { p->original_asbd_stream = streams[i]; err = CA_GET(p->original_asbd_stream, kAudioStreamPropertyPhysicalFormat, &p->original_asbd); CHECK_CA_WARN("could not get current physical stream format"); ca_change_physical_format_sync(ao, streams[i], best_asbd); break; } } coreaudio_error: return; }
static AudioChannelLayout *ca_layout_to_custom_layout(struct ao *ao, void *talloc_ctx, AudioChannelLayout *l) { AudioChannelLayoutTag tag = l->mChannelLayoutTag; AudioChannelLayout *r; OSStatus err; if (tag == kAudioChannelLayoutTag_UseChannelDescriptions) return l; if (tag == kAudioChannelLayoutTag_UseChannelBitmap) { uint32_t psize; err = AudioFormatGetPropertyInfo( kAudioFormatProperty_ChannelLayoutForBitmap, sizeof(uint32_t), &l->mChannelBitmap, &psize); CHECK_CA_ERROR("failed to convert channel bitmap to descriptions (info)"); r = talloc_size(NULL, psize); err = AudioFormatGetProperty( kAudioFormatProperty_ChannelLayoutForBitmap, sizeof(uint32_t), &l->mChannelBitmap, &psize, r); CHECK_CA_ERROR("failed to convert channel bitmap to descriptions (get)"); } else { uint32_t psize; err = AudioFormatGetPropertyInfo( kAudioFormatProperty_ChannelLayoutForTag, sizeof(AudioChannelLayoutTag), &l->mChannelLayoutTag, &psize); r = talloc_size(NULL, psize); CHECK_CA_ERROR("failed to convert channel tag to descriptions (info)"); err = AudioFormatGetProperty( kAudioFormatProperty_ChannelLayoutForTag, sizeof(AudioChannelLayoutTag), &l->mChannelLayoutTag, &psize, r); CHECK_CA_ERROR("failed to convert channel tag to descriptions (get)"); } MP_VERBOSE(ao, "converted input channel layout:\n"); ca_log_layout(ao, MSGL_V, l); return r; coreaudio_error: return NULL; }
static int set_volume(struct ao *ao, struct ao_control_vol *vol) { struct priv *p = ao->priv; float auvol = (vol->left + vol->right) / 200.0; OSStatus err = AudioUnitSetParameter(p->audio_unit, kHALOutputParam_Volume, kAudioUnitScope_Global, 0, auvol, 0); CHECK_CA_ERROR("could not set HAL output volume"); return CONTROL_TRUE; coreaudio_error: return CONTROL_ERROR; }
void ca_get_device_list(struct ao *ao, struct ao_device_list *list) { AudioDeviceID *devs; size_t n_devs; OSStatus err = CA_GET_ARY(kAudioObjectSystemObject, kAudioHardwarePropertyDevices, &devs, &n_devs); CHECK_CA_ERROR("Failed to get list of output devices."); for (int i = 0; i < n_devs; i++) { char name[32]; char *desc; sprintf(name, "%d", devs[i]); err = CA_GET_STR(devs[i], kAudioObjectPropertyName, &desc); if (err != noErr) desc = "Unknown"; ao_device_list_add(list, ao, &(struct ao_device_desc){name, desc}); }
bool ca_device_supports_digital(struct ao *ao, AudioDeviceID device) { AudioStreamID *streams = NULL; size_t n_streams; /* Retrieve all the output streams. */ OSStatus err = CA_GET_ARY_O(device, kAudioDevicePropertyStreams, &streams, &n_streams); CHECK_CA_ERROR("could not get number of streams."); for (int i = 0; i < n_streams; i++) { if (ca_stream_supports_digital(ao, streams[i])) { talloc_free(streams); return true; } } talloc_free(streams); coreaudio_error: return false; }
OSStatus ca_select_device(struct ao *ao, char* name, AudioDeviceID *device) { OSStatus err = noErr; *device = kAudioObjectUnknown; if (name && name[0]) { CFStringRef uid = cfstr_from_cstr(name); AudioValueTranslation v = (AudioValueTranslation) { .mInputData = &uid, .mInputDataSize = sizeof(CFStringRef), .mOutputData = device, .mOutputDataSize = sizeof(*device), }; uint32_t size = sizeof(AudioValueTranslation); AudioObjectPropertyAddress p_addr = (AudioObjectPropertyAddress) { .mSelector = kAudioHardwarePropertyDeviceForUID, .mScope = kAudioObjectPropertyScopeGlobal, .mElement = kAudioObjectPropertyElementMaster, }; err = AudioObjectGetPropertyData( kAudioObjectSystemObject, &p_addr, 0, 0, &size, &v); CFRelease(uid); CHECK_CA_ERROR("unable to query for device UID"); } else {
bool ca_stream_supports_digital(struct ao *ao, AudioStreamID stream) { AudioStreamRangedDescription *formats = NULL; size_t n_formats; OSStatus err = CA_GET_ARY(stream, kAudioStreamPropertyAvailablePhysicalFormats, &formats, &n_formats); CHECK_CA_ERROR("Could not get number of stream formats."); for (int i = 0; i < n_formats; i++) { AudioStreamBasicDescription asbd = formats[i].mFormat; ca_print_asbd(ao, "supported format:", &(asbd)); if (ca_format_is_digital(asbd)) { talloc_free(formats); return true; } } talloc_free(formats); coreaudio_error: return false; }
static bool init_audiounit(struct ao *ao, AudioStreamBasicDescription asbd) { OSStatus err; uint32_t size; struct priv *p = ao->priv; AudioComponentDescription desc = (AudioComponentDescription) { .componentType = kAudioUnitType_Output, .componentSubType = (ao->device) ? kAudioUnitSubType_HALOutput : kAudioUnitSubType_DefaultOutput, .componentManufacturer = kAudioUnitManufacturer_Apple, .componentFlags = 0, .componentFlagsMask = 0, }; AudioComponent comp = AudioComponentFindNext(NULL, &desc); if (comp == NULL) { MP_ERR(ao, "unable to find audio component\n"); goto coreaudio_error; } err = AudioComponentInstanceNew(comp, &(p->audio_unit)); CHECK_CA_ERROR("unable to open audio component"); err = AudioUnitInitialize(p->audio_unit); CHECK_CA_ERROR_L(coreaudio_error_component, "unable to initialize audio unit"); size = sizeof(AudioStreamBasicDescription); err = AudioUnitSetProperty(p->audio_unit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 0, &asbd, size); CHECK_CA_ERROR_L(coreaudio_error_audiounit, "unable to set the input format on the audio unit"); err = AudioUnitSetProperty(p->audio_unit, kAudioOutputUnitProperty_CurrentDevice, kAudioUnitScope_Global, 0, &p->device, sizeof(p->device)); CHECK_CA_ERROR_L(coreaudio_error_audiounit, "can't link audio unit to selected device"); p->hw_latency_us = ca_get_hardware_latency(ao); AURenderCallbackStruct render_cb = (AURenderCallbackStruct) { .inputProc = render_cb_lpcm, .inputProcRefCon = ao, }; err = AudioUnitSetProperty(p->audio_unit, kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Input, 0, &render_cb, sizeof(AURenderCallbackStruct)); CHECK_CA_ERROR_L(coreaudio_error_audiounit, "unable to set render callback on audio unit"); return true; coreaudio_error_audiounit: AudioUnitUninitialize(p->audio_unit); coreaudio_error_component: AudioComponentInstanceDispose(p->audio_unit); coreaudio_error: return false; } static void stop(struct ao *ao) { struct priv *p = ao->priv; OSStatus err = AudioOutputUnitStop(p->audio_unit); CHECK_CA_WARN("can't stop audio unit"); } static void start(struct ao *ao) { struct priv *p = ao->priv; OSStatus err = AudioOutputUnitStart(p->audio_unit); CHECK_CA_WARN("can't start audio unit"); } static void uninit(struct ao *ao) { struct priv *p = ao->priv; AudioOutputUnitStop(p->audio_unit); AudioUnitUninitialize(p->audio_unit); AudioComponentInstanceDispose(p->audio_unit); if (p->original_asbd.mFormatID) { OSStatus err = CA_SET(p->original_asbd_stream, kAudioStreamPropertyPhysicalFormat, &p->original_asbd); CHECK_CA_WARN("could not restore physical stream format"); } } static OSStatus hotplug_cb(AudioObjectID id, UInt32 naddr, const AudioObjectPropertyAddress addr[], void *ctx) { reinit_device(ctx); ao_hotplug_event(ctx); return noErr; }