/* * 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 "ao.h" #include "audio/format.h" #include "audio/out/ao_coreaudio_chmap.h" #include "audio/out/ao_coreaudio_utils.h" #include "common/common.h" #include "common/msg.h" #include "internal.h" #include "osdep/timer.h" #include "ta/ta_talloc.h" #import #import #import #import #import @interface AVObserver : NSObject { struct ao *ao; } - (void)handleRestartNotification:(NSNotification*)notification; @end struct priv { AVSampleBufferAudioRenderer *renderer; AVSampleBufferRenderSynchronizer *synchronizer; dispatch_queue_t queue; CMAudioFormatDescriptionRef format_description; AVObserver *observer; int64_t end_time_av; }; static int64_t CMTimeGetNanoseconds(CMTime time) { time = CMTimeConvertScale(time, 1000000000, kCMTimeRoundingMethod_Default); return time.value; } static CMTime CMTimeFromNanoseconds(int64_t time) { return CMTimeMake(time, 1000000000); } static void feed(struct ao *ao) { struct priv *p = ao->priv; int samplerate = ao->samplerate; int sstride = ao->sstride; CMBlockBufferRef block_buffer = NULL; CMSampleBufferRef sample_buffer = NULL; OSStatus err; int request_sample_count = samplerate / 10; int buffer_size = request_sample_count * sstride; void *data[] = {CFAllocatorAllocate(NULL, buffer_size, 0)}; int64_t cur_time_av = CMTimeGetNanoseconds([p->synchronizer currentTime]); int64_t cur_time_mp = mp_time_ns(); int64_t end_time_av = MPMAX(p->end_time_av, cur_time_av); int64_t time_delta = CMTimeGetNanoseconds(CMTimeMake(request_sample_count, samplerate)); int real_sample_count = ao_read_data_nonblocking(ao, data, request_sample_count, end_time_av - cur_time_av + cur_time_mp + time_delta); if (real_sample_count == 0) { // avoid spinning by blocking the thread mp_sleep_ns(10000000); goto finish; } if ((err = CMBlockBufferCreateWithMemoryBlock( NULL, data[0], buffer_size, NULL, NULL, 0, real_sample_count * sstride, 0, &block_buffer )) != noErr) { MP_FATAL(ao, "failed to create block buffer\n"); MP_VERBOSE(ao, "CMBlockBufferCreateWithMemoryBlock returned %d\n", err); goto error; } data[0] = NULL; CMSampleTimingInfo sample_timing_into[] = {(CMSampleTimingInfo) { .duration = CMTimeMake(1, samplerate), .presentationTimeStamp = CMTimeFromNanoseconds(end_time_av), .decodeTimeStamp = kCMTimeInvalid }}; size_t sample_size_array[] = {sstride}; if ((err = CMSampleBufferCreateReady( NULL, block_buffer, p->format_description, real_sample_count, 1, sample_timing_into, 1, sample_size_array, &sample_buffer )) != noErr) { MP_FATAL(ao, "failed to create sample buffer\n"); MP_VERBOSE(ao, "CMSampleBufferCreateReady returned %d\n", err); goto error; } [p->renderer enqueueSampleBuffer:sample_buffer]; time_delta = CMTimeGetNanoseconds(CMTimeMake(real_sample_count, samplerate)); p->end_time_av = end_time_av + time_delta; goto finish; error: ao_request_reload(ao); finish: if (data[0]) CFAllocatorDeallocate(NULL, data[0]); if (block_buffer) CFRelease(block_buffer); if (sample_buffer) CFRelease(sample_buffer); } static void start(struct ao *ao) { struct priv *p = ao->priv; p->end_time_av = -1; [p->synchronizer setRate:1]; [p->renderer requestMediaDataWhenReadyOnQueue:p->queue usingBlock:^{ feed(ao); }]; } static void stop(struct ao *ao) { struct priv *p = ao->priv; dispatch_sync(p->queue, ^{ [p->renderer stopRequestingMediaData]; [p->renderer flush]; [p->synchronizer setRate:0]; }); } static int control(struct ao *ao, enum aocontrol cmd, void *arg) { struct priv *p = ao->priv; switch (cmd) { case AOCONTROL_GET_MUTE: *(bool*)arg = [p->renderer isMuted]; return CONTROL_OK; case AOCONTROL_GET_VOLUME: *(float*)arg = [p->renderer volume] * 100; return CONTROL_OK; case AOCONTROL_SET_MUTE: [p->renderer setMuted:*(bool*)arg]; return CONTROL_OK; case AOCONTROL_SET_VOLUME: [p->renderer setVolume:*(float*)arg / 100]; return CONTROL_OK; default: return CONTROL_UNKNOWN; } } @implementation AVObserver - (instancetype)initWithAO:(struct ao*)_ao { self = [super init]; if (self) { ao = _ao; } return self; } - (void)handleRestartNotification:(NSNotification*)notification { char *name = cfstr_get_cstr((CFStringRef)notification.name); MP_WARN(ao, "restarting due to system notification; this will cause desync\n"); MP_VERBOSE(ao, "notification name: %s\n", name); talloc_free(name); stop(ao); start(ao); } @end static int init(struct ao *ao) { struct priv *p = ao->priv; AudioChannelLayout *layout = NULL; #if TARGET_OS_IPHONE AVAudioSession *instance = AVAudioSession.sharedInstance; NSInteger maxChannels = instance.maximumOutputNumberOfChannels; NSInteger prefChannels = MIN(maxChannels, ao->channels.num); [instance setCategory:AVAudioSessionCategoryPlayback error:nil]; [instance setMode:AVAudioSessionModeMoviePlayback error:nil]; [instance setActive:YES error:nil]; [instance setPreferredOutputNumberOfChannels:prefChannels error:nil]; #endif if ((p->renderer = [[AVSampleBufferAudioRenderer alloc] init]) == nil) { MP_FATAL(ao, "failed to create audio renderer\n"); MP_VERBOSE(ao, "AVSampleBufferAudioRenderer failed to initialize\n"); goto error; } if ((p->synchronizer = [[AVSampleBufferRenderSynchronizer alloc] init]) == nil) { MP_FATAL(ao, "failed to create rendering synchronizer\n"); MP_VERBOSE(ao, "AVSampleBufferRenderSynchronizer failed to initialize\n"); goto error; } if ((p->queue = dispatch_queue_create( "avfoundation event", dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_USER_INTERACTIVE, 0) )) == NULL) { MP_FATAL(ao, "failed to create dispatch queue\n"); MP_VERBOSE(ao, "dispatch_queue_create failed\n"); goto error; } if (ao->device && ao->device[0]) { [p->renderer setAudioOutputDeviceUniqueID:(NSString*)cfstr_from_cstr(ao->device)]; } [p->synchronizer addRenderer:p->renderer]; if (@available(tvOS 14.5, iOS 14.5, macOS 11.3, *)) { [p->synchronizer setDelaysRateChangeUntilHasSufficientMediaData:NO]; } if (af_fmt_is_spdif(ao->format)) { MP_FATAL(ao, "avfoundation does not support SPDIF\n"); #if HAVE_COREAUDIO MP_FATAL(ao, "please use coreaudio_exclusive instead\n"); #endif goto error; } // AVSampleBufferAudioRenderer only supports interleaved formats ao->format = af_fmt_from_planar(ao->format); if (af_fmt_is_planar(ao->format)) { MP_FATAL(ao, "planar audio formats are unsupported\n"); goto error; } AudioStreamBasicDescription asbd; ca_fill_asbd(ao, &asbd); size_t layout_size = sizeof(AudioChannelLayout) + (ao->channels.num - 1) * sizeof(AudioChannelDescription); layout = talloc_size(ao, layout_size); layout->mChannelLayoutTag = kAudioChannelLayoutTag_UseChannelDescriptions; layout->mNumberChannelDescriptions = ao->channels.num; for (int i = 0; i < ao->channels.num; ++i) { AudioChannelDescription *desc = layout->mChannelDescriptions + i; desc->mChannelFlags = kAudioChannelFlags_AllOff; desc->mChannelLabel = mp_speaker_id_to_ca_label(ao->channels.speaker[i]); } void *talloc_ctx = talloc_new(NULL); AudioChannelLayout *std_layout = ca_find_standard_layout(talloc_ctx, layout); memmove(layout, std_layout, sizeof(AudioChannelLayout)); talloc_free(talloc_ctx); ca_log_layout(ao, MSGL_V, layout); OSStatus err; if ((err = CMAudioFormatDescriptionCreate( NULL, &asbd, layout_size, layout, 0, NULL, NULL, &p->format_description )) != noErr) { MP_FATAL(ao, "failed to create audio format description\n"); MP_VERBOSE(ao, "CMAudioFormatDescriptionCreate returned %d\n", err); goto error; } talloc_free(layout); layout = NULL; // AVSampleBufferAudioRenderer read ahead aggressively ao->device_buffer = ao->samplerate * 2; p->observer = [[AVObserver alloc] initWithAO:ao]; NSNotificationCenter *center = [NSNotificationCenter defaultCenter]; [center addObserver:p->observer selector:@selector(handleRestartNotification:) name:AVSampleBufferAudioRendererOutputConfigurationDidChangeNotification object:p->renderer]; [center addObserver:p->observer selector:@selector(handleRestartNotification:) name:AVSampleBufferAudioRendererWasFlushedAutomaticallyNotification object:p->renderer]; return CONTROL_OK; error: talloc_free(layout); if (p->renderer) [p->renderer release]; if (p->synchronizer) [p->synchronizer release]; if (p->queue) dispatch_release(p->queue); if (p->format_description) CFRelease(p->format_description); #if TARGET_OS_IPHONE [AVAudioSession.sharedInstance setActive:NO withOptions:AVAudioSessionSetActiveOptionNotifyOthersOnDeactivation error:nil ]; #endif return CONTROL_ERROR; } static void uninit(struct ao *ao) { struct priv *p = ao->priv; stop(ao); [p->renderer release]; [p->synchronizer release]; dispatch_release(p->queue); CFRelease(p->format_description); [[NSNotificationCenter defaultCenter] removeObserver:p->observer]; [p->observer release]; #if TARGET_OS_IPHONE [AVAudioSession.sharedInstance setActive:NO withOptions:AVAudioSessionSetActiveOptionNotifyOthersOnDeactivation error:nil ]; #endif } #define OPT_BASE_STRUCT struct priv const struct ao_driver audio_out_avfoundation = { .description = "AVFoundation AVSampleBufferAudioRenderer", .name = "avfoundation", .uninit = uninit, .init = init, .control = control, .reset = stop, .start = start, .list_devs = ca_get_device_list, .priv_size = sizeof(struct priv), };