diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-15 20:36:56 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-15 20:36:56 +0000 |
commit | 51de1d8436100f725f3576aefa24a2bd2057bc28 (patch) | |
tree | c6d1d5264b6d40a8d7ca34129f36b7d61e188af3 /audio/out/ao_audiounit.m | |
parent | Initial commit. (diff) | |
download | mpv-0a581925d1e517e0dd867d02641216b9f5c51c5d.tar.xz mpv-0a581925d1e517e0dd867d02641216b9f5c51c5d.zip |
Adding upstream version 0.37.0.upstream/0.37.0
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'audio/out/ao_audiounit.m')
-rw-r--r-- | audio/out/ao_audiounit.m | 260 |
1 files changed, 260 insertions, 0 deletions
diff --git a/audio/out/ao_audiounit.m b/audio/out/ao_audiounit.m new file mode 100644 index 0000000..85b1226 --- /dev/null +++ b/audio/out/ao_audiounit.m @@ -0,0 +1,260 @@ +/* + * 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 <http://www.gnu.org/licenses/>. + */ + +#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 <AudioUnit/AudioUnit.h> +#import <CoreAudio/CoreAudioTypes.h> +#import <AudioToolbox/AudioToolbox.h> +#import <AVFoundation/AVFoundation.h> +#import <mach/mach_time.h> + +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_ns(); + end += MP_TIME_S_TO_NS(p->device_latency); + end += ca_get_latency(ts) + ca_frames_to_ns(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), +}; |