/* * This file is part of mpv. * * mpv is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * mpv is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with mpv. If not, see . */ #include "config.h" #include "ao.h" #include "internal.h" #include "audio/format.h" #include "osdep/timer.h" #include "options/m_option.h" #include "common/msg.h" #include "ao_coreaudio_utils.h" #include "ao_coreaudio_chmap.h" #import #import #import #import #import struct priv { AudioUnit audio_unit; double device_latency; }; static OSStatus au_get_ary(AudioUnit unit, AudioUnitPropertyID inID, AudioUnitScope inScope, AudioUnitElement inElement, void **data, UInt32 *outDataSize) { OSStatus err; err = AudioUnitGetPropertyInfo(unit, inID, inScope, inElement, outDataSize, NULL); CHECK_CA_ERROR_SILENT_L(coreaudio_error); *data = talloc_zero_size(NULL, *outDataSize); err = AudioUnitGetProperty(unit, inID, inScope, inElement, *data, outDataSize); CHECK_CA_ERROR_SILENT_L(coreaudio_error_free); return err; coreaudio_error_free: talloc_free(*data); coreaudio_error: return err; } static AudioChannelLayout *convert_layout(AudioChannelLayout *layout, UInt32* size) { AudioChannelLayoutTag tag = layout->mChannelLayoutTag; AudioChannelLayout *new_layout; if (tag == kAudioChannelLayoutTag_UseChannelDescriptions) return layout; else if (tag == kAudioChannelLayoutTag_UseChannelBitmap) AudioFormatGetPropertyInfo(kAudioFormatProperty_ChannelLayoutForBitmap, sizeof(UInt32), &layout->mChannelBitmap, size); else AudioFormatGetPropertyInfo(kAudioFormatProperty_ChannelLayoutForTag, sizeof(AudioChannelLayoutTag), &tag, size); new_layout = talloc_zero_size(NULL, *size); if (!new_layout) { talloc_free(layout); return NULL; } if (tag == kAudioChannelLayoutTag_UseChannelBitmap) AudioFormatGetProperty(kAudioFormatProperty_ChannelLayoutForBitmap, sizeof(UInt32), &layout->mChannelBitmap, size, new_layout); else AudioFormatGetProperty(kAudioFormatProperty_ChannelLayoutForTag, sizeof(AudioChannelLayoutTag), &tag, size, new_layout); new_layout->mChannelLayoutTag = kAudioChannelLayoutTag_UseChannelDescriptions; talloc_free(layout); return new_layout; } static OSStatus render_cb_lpcm(void *ctx, AudioUnitRenderActionFlags *aflags, const AudioTimeStamp *ts, UInt32 bus, UInt32 frames, AudioBufferList *buffer_list) { struct ao *ao = ctx; struct priv *p = ao->priv; void *planes[MP_NUM_CHANNELS] = {0}; for (int n = 0; n < ao->num_planes; n++) planes[n] = buffer_list->mBuffers[n].mData; int64_t end = mp_time_us(); end += p->device_latency * 1e6; end += ca_get_latency(ts) + ca_frames_to_us(ao, frames); ao_read_data(ao, planes, frames, end); return noErr; } static bool init_audiounit(struct ao *ao) { AudioStreamBasicDescription asbd; OSStatus err; uint32_t size; AudioChannelLayout *layout = NULL; struct priv *p = ao->priv; AVAudioSession *instance = AVAudioSession.sharedInstance; AVAudioSessionPortDescription *port = nil; NSInteger maxChannels = instance.maximumOutputNumberOfChannels; NSInteger prefChannels = MIN(maxChannels, ao->channels.num); MP_VERBOSE(ao, "max channels: %ld, requested: %d\n", maxChannels, (int)ao->channels.num); [instance setCategory:AVAudioSessionCategoryPlayback error:nil]; [instance setMode:AVAudioSessionModeMoviePlayback error:nil]; [instance setActive:YES error:nil]; [instance setPreferredOutputNumberOfChannels:prefChannels error:nil]; AudioComponentDescription desc = (AudioComponentDescription) { .componentType = kAudioUnitType_Output, .componentSubType = kAudioUnitSubType_RemoteIO, .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"); err = au_get_ary(p->audio_unit, kAudioUnitProperty_AudioChannelLayout, kAudioUnitScope_Output, 0, (void**)&layout, &size); CHECK_CA_ERROR_L(coreaudio_error_audiounit, "unable to retrieve audio unit channel layout"); MP_VERBOSE(ao, "AU channel layout tag: %x (%x)\n", layout->mChannelLayoutTag, layout->mChannelBitmap); layout = convert_layout(layout, &size); if (!layout) { MP_ERR(ao, "unable to convert channel layout to list format\n"); goto coreaudio_error_audiounit; } for (UInt32 i = 0; i < layout->mNumberChannelDescriptions; i++) { MP_VERBOSE(ao, "channel map: %i: %u\n", i, layout->mChannelDescriptions[i].mChannelLabel); } if (af_fmt_is_spdif(ao->format) || instance.outputNumberOfChannels <= 2) { ao->channels = (struct mp_chmap)MP_CHMAP_INIT_STEREO; MP_VERBOSE(ao, "using stereo output\n"); } else { ao->channels.num = (uint8_t)layout->mNumberChannelDescriptions; for (UInt32 i = 0; i < layout->mNumberChannelDescriptions; i++) { ao->channels.speaker[i] = ca_label_to_mp_speaker_id(layout->mChannelDescriptions[i].mChannelLabel); } MP_VERBOSE(ao, "using standard channel mapping\n"); } ca_fill_asbd(ao, &asbd); 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"); 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"); talloc_free(layout); return true; coreaudio_error_audiounit: AudioUnitUninitialize(p->audio_unit); coreaudio_error_component: AudioComponentInstanceDispose(p->audio_unit); coreaudio_error: talloc_free(layout); 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; AVAudioSession *instance = AVAudioSession.sharedInstance; p->device_latency = [instance outputLatency]; 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); [AVAudioSession.sharedInstance setActive:NO withOptions:AVAudioSessionSetActiveOptionNotifyOthersOnDeactivation error:nil]; } static int init(struct ao *ao) { if (!init_audiounit(ao)) goto coreaudio_error; return CONTROL_OK; coreaudio_error: return CONTROL_ERROR; } #define OPT_BASE_STRUCT struct priv const struct ao_driver audio_out_audiounit = { .description = "AudioUnit (iOS)", .name = "audiounit", .uninit = uninit, .init = init, .reset = stop, .start = start, .priv_size = sizeof(struct priv), };