[vlc-commits] [Git][videolan/vlc][master] 15 commits: modules: move apple aouts to common folder

Steve Lhomme (@robUx4) gitlab at videolan.org
Tue May 7 07:32:50 UTC 2024



Steve Lhomme pushed to branch master at VideoLAN / VLC


Commits:
56a0d3d3 by Thomas Guillem at 2024-05-07T06:35:17+00:00
modules: move apple aouts to common folder

- - - - -
2c5912df by Thomas Guillem at 2024-05-07T06:35:17+00:00
audiounit_ios: always set the preferred channel count

So that we don't have to reset it after playback.
Also, warn when the preferred channel count doesn't match with the
input.

- - - - -
7bbbb16f by Thomas Guillem at 2024-05-07T06:35:17+00:00
audiounit_ios: rename avas_setPreferredNumberOfChannels

- - - - -
ab8ebdeb by Thomas Guillem at 2024-05-07T06:35:17+00:00
audiounit_ios: move setPreferredSampleRate in avas_PrepareFormat

- - - - -
53e033bb by Thomas Guillem at 2024-05-07T06:35:17+00:00
audiounit_ios: log more NSErrror

- - - - -
67149f7c by Thomas Guillem at 2024-05-07T06:35:17+00:00
Revert "aout iOS: add support for spatial audio"

This reverts commit 744248f5ee6e2ffa205e2282036f9450025bc80f.

SpatialAudio doesn't work with audiounit. This will be added back in the
avsamplebuffer aout.

- - - - -
8d799163 by Thomas Guillem at 2024-05-07T06:35:17+00:00
audiounit_ios: handle spatial audio

Don't ask the core to downmix to stereo when spatialaudio is handled.
Notify when we are playing multi channels.

This is not supported by the audiounit_ios module but will be supported
by the future avsamplebuffer module.

- - - - -
2dea2862 by Thomas Guillem at 2024-05-07T06:35:17+00:00
audiounit_ios: replace SessionManager with a static atomic

- - - - -
da27196e by Thomas Guillem at 2024-05-07T06:35:17+00:00
audiounit_ios: make avas_SetActive standalone

And not module (sys) dependant.

- - - - -
d35fce0a by Thomas Guillem at 2024-05-07T06:35:17+00:00
audiounit_ios: make avas_GetPortType standalone

And not module (sys) dependant.

- - - - -
53d58abb by Thomas Guillem at 2024-05-07T06:35:17+00:00
audiounit_ios: make avas_PrepareFormat standalone

And not module (sys) dependant.

- - - - -
49ea2f35 by Thomas Guillem at 2024-05-07T06:35:17+00:00
aout: apple: export channel_layout_MapFromVLC

This will be used by the future avsamplebuffer aout.

- - - - -
c1420e41 by Thomas Guillem at 2024-05-07T06:35:17+00:00
aout: apple: export avas helpers

- - - - -
97972a25 by Thomas Guillem at 2024-05-07T06:35:17+00:00
audiounit_ios: decrease priority

No changes since the other aout module is macOS only (auhal) and won't
interfere.

- - - - -
63f3e396 by Thomas Guillem at 2024-05-07T06:35:17+00:00
aout: add avsamplebuffer

This module enables SpatialAudio when playing multi channels content.

This module has finer A/V sync delay:
 - 15-40ms on speaker/HDMI
 - -80ms with AirPlay
 - 80ms with bluetooth devices (was 250ms before).

Airplay playback is now more stable, the VLC delay (via
aout_TimingReport) is less than 100ms, so audio and video will play
almost instantaneously.

With audiounit_ios, the delay was 2 seconds making it harder to sync
audio and video (since input tracks had to be delayed too).

Note that even if the audio seems to be playing from the VLC side, the
airplay will be silent for the first 2seconds. This can be improved by
increasing the pts-delay (file-caching): 7seconds of pts-delay remove
this latency (from 2secs to 100ms), but this is not something that could
be enabled by default.

One other improvement is that the airplay device will stop immediately
when pausing/stopping/seeking.

This module has a priority of 100, less than auhal, that can handle
passthrough, but more than audiunit_ios. Therefore, this module will be
the default one on iOS/tvOS, but can be selected by the user on macOS
(or deselected on iOS/tvOS). This is temporary as it might be
interesting for macOS users to use this aout as it can handle Spatial
Audio.

- - - - -


12 changed files:

- modules/audio_output/Makefile.am
- modules/audio_output/audiounit_ios.m → modules/audio_output/apple/audiounit_ios.m
- modules/audio_output/auhal.c → modules/audio_output/apple/auhal.c
- + modules/audio_output/apple/avaudiosession_common.h
- + modules/audio_output/apple/avaudiosession_common.m
- + modules/audio_output/apple/avsamplebuffer.m
- + modules/audio_output/apple/channel_layout.c
- + modules/audio_output/apple/channel_layout.h
- modules/audio_output/coreaudio_common.c → modules/audio_output/apple/coreaudio_common.c
- modules/audio_output/coreaudio_common.h → modules/audio_output/apple/coreaudio_common.h
- modules/audio_output/meson.build
- po/POTFILES.in


Changes:

=====================================
modules/audio_output/Makefile.am
=====================================
@@ -117,20 +117,35 @@ if HAVE_WIN32_DESKTOP
 aout_LTLIBRARIES += libwaveout_plugin.la
 endif
 
-libauhal_plugin_la_SOURCES = audio_output/auhal.c \
-	audio_output/coreaudio_common.c audio_output/coreaudio_common.h
+libavsamplebuffer_plugin_la_SOURCES = audio_output/apple/avsamplebuffer.m \
+	audio_output/apple/channel_layout.c
+if HAVE_IOS
+libavsamplebuffer_plugin_la_SOURCES += audio_output/apple/avaudiosession_common.m
+endif
+if HAVE_TVOS
+libavsamplebuffer_plugin_la_SOURCES += audio_output/apple/avaudiosession_common.m
+endif
+libavsamplebuffer_plugin_la_LDFLAGS = $(AM_LDFLAGS) -rpath '$(aoutdir)' \
+	-Wl,-framework,CoreMedia,-framework,Foundation,-framework,AVFoundation
+libavsamplebuffer_plugin_la_OBJCFLAGS = $(AM_OBJCFLAGS) -fobjc-arc
+
+libauhal_plugin_la_SOURCES = audio_output/apple/auhal.c \
+	audio_output/apple/coreaudio_common.c audio_output/apple/coreaudio_common.h \
+	audio_output/apple/channel_layout.c
+
 libauhal_plugin_la_LDFLAGS = $(AM_LDFLAGS) -rpath '$(aoutdir)' \
 	-Wl,-framework,CoreAudio,-framework,AudioUnit,-framework,AudioToolbox,-framework,CoreServices
 if HAVE_OSX
-aout_LTLIBRARIES += libauhal_plugin.la
+aout_LTLIBRARIES += libauhal_plugin.la libavsamplebuffer_plugin.la
 endif
-libaudiounit_ios_plugin_la_SOURCES = audio_output/audiounit_ios.m \
-	audio_output/coreaudio_common.c audio_output/coreaudio_common.h
+libaudiounit_ios_plugin_la_SOURCES = audio_output/apple/audiounit_ios.m \
+	audio_output/apple/coreaudio_common.c audio_output/apple/coreaudio_common.h \
+	audio_output/apple/channel_layout.c audio_output/apple/avaudiosession_common.m
 libaudiounit_ios_plugin_la_LDFLAGS = $(AM_LDFLAGS) -rpath '$(aoutdir)' \
 	-Wl,-framework,Foundation,-framework,CoreAudio,-framework,AudioToolbox,-framework,UIKit,-framework,AVFoundation
 if HAVE_IOS_OR_TVOS
-aout_LTLIBRARIES += libaudiounit_ios_plugin.la
+aout_LTLIBRARIES += libaudiounit_ios_plugin.la libavsamplebuffer_plugin.la
 endif
 if HAVE_XROS
-aout_LTLIBRARIES += libaudiounit_ios_plugin.la
+aout_LTLIBRARIES += libaudiounit_ios_plugin.la libavsamplebuffer_plugin.la
 endif


=====================================
modules/audio_output/audiounit_ios.m → modules/audio_output/apple/audiounit_ios.m
=====================================
@@ -30,6 +30,7 @@
 #import <Foundation/Foundation.h>
 #import <AVFoundation/AVFoundation.h>
 #import <mach/mach_time.h>
+#import "avaudiosession_common.h"
 
 #pragma mark -
 #pragma mark private declarations
@@ -56,68 +57,6 @@ static const struct {
       AU_DEV_ENCODED }, /* This can also be forced with the --spdif option */
 };
 
-#if ((__IPHONE_OS_VERSION_MAX_ALLOWED && __IPHONE_OS_VERSION_MAX_ALLOWED < 150000) || (__TV_OS_MAX_VERSION_ALLOWED && __TV_OS_MAX_VERSION_ALLOWED < 150000))
-
-NSString *const AVAudioSessionSpatialAudioEnabledKey = @"AVAudioSessionSpatializationEnabledKey";
-NSString *const AVAudioSessionSpatialPlaybackCapabilitiesChangedNotification = @"AVAudioSessionSpatialPlaybackCapabilitiesChangedNotification";
-
- at interface AVAudioSession (iOS15RoutingConfiguration)
-- (BOOL)setSupportsMultichannelContent:(BOOL)inValue error:(NSError **)outError;
- at end
-
- at interface AVAudioSessionPortDescription (iOS15RoutingConfiguration)
- at property (readonly, getter=isSpatialAudioEnabled) BOOL spatialAudioEnabled;
- at end
-
-#endif
-
- at interface SessionManager : NSObject
-{
-    NSMutableSet *_registeredInstances;
-}
-+ (SessionManager *)sharedInstance;
-- (void)addAoutInstance:(AoutWrapper *)wrapperInstance;
-- (NSInteger)removeAoutInstance:(AoutWrapper *)wrapperInstance;
- at end
-
- at implementation SessionManager
-+ (SessionManager *)sharedInstance
-{
-    static SessionManager *sharedInstance = nil;
-    static dispatch_once_t pred;
-
-    dispatch_once(&pred, ^{
-        sharedInstance = [SessionManager new];
-    });
-
-    return sharedInstance;
-}
-
-- (instancetype)init
-{
-    self = [super init];
-    if (self) {
-        _registeredInstances = [[NSMutableSet alloc] init];
-    }
-    return self;
-}
-
-- (void)addAoutInstance:(AoutWrapper *)wrapperInstance
-{
-    @synchronized(_registeredInstances) {
-        [_registeredInstances addObject:wrapperInstance];
-    }
-}
-
-- (NSInteger)removeAoutInstance:(AoutWrapper *)wrapperInstance
-{
-    @synchronized(_registeredInstances) {
-        [_registeredInstances removeObject:wrapperInstance];
-        return _registeredInstances.count;
-    }
-}
- at end
-
 /*****************************************************************************
  * aout_sys_t: private audio output method descriptor
  *****************************************************************************
@@ -134,8 +73,6 @@ typedef struct
     AudioUnit au_unit;
     bool      b_muted;
     bool      b_stopped;
-    bool      b_preferred_channels_set;
-    bool      b_spatial_audio_supported;
     enum au_dev au_dev;
 
     /* For debug purpose, to print when specific latency changed */
@@ -150,14 +87,6 @@ typedef struct
 /* Soft volume helper */
 #include "audio_output/volume.h"
 
-enum port_type
-{
-    PORT_TYPE_DEFAULT,
-    PORT_TYPE_USB,
-    PORT_TYPE_HDMI,
-    PORT_TYPE_HEADPHONES
-};
-
 static vlc_tick_t
 GetLatency(audio_output_t *p_aout)
 {
@@ -240,215 +169,8 @@ GetLatency(audio_output_t *p_aout)
     }
 }
 
-- (void)handleSpatialCapabilityChange:(NSNotification *)notification
-{
-    if (@available(iOS 15.0, tvOS 15.0, *)) {
-        audio_output_t *p_aout = [self aout];
-        struct aout_sys_t *p_sys = p_aout->sys;
-        NSDictionary *userInfo = notification.userInfo;
-        BOOL spatialAudioEnabled =
-            [[userInfo valueForKey:AVAudioSessionSpatialAudioEnabledKey] boolValue];
-
-        msg_Dbg(p_aout, "Spatial Audio availability changed: %i", spatialAudioEnabled);
-
-        if (spatialAudioEnabled) {
-            aout_RestartRequest(p_aout, false);
-        }
-    }
-}
 @end
 
-static void
-avas_setPreferredNumberOfChannels(audio_output_t *p_aout,
-                                  const audio_sample_format_t *fmt)
-{
-    aout_sys_t *p_sys = p_aout->sys;
-
-    if (aout_BitsPerSample(fmt->i_format) == 0)
-        return; /* Don't touch the number of channels for passthrough */
-
-    AVAudioSession *instance = p_sys->avInstance;
-    NSInteger max_channel_count = [instance maximumOutputNumberOfChannels];
-    unsigned channel_count = aout_FormatNbChannels(fmt);
-
-    /* Increase the preferred number of output channels if possible */
-    if (channel_count > 2 && max_channel_count > 2)
-    {
-        channel_count = __MIN(channel_count, max_channel_count);
-        bool success = [instance setPreferredOutputNumberOfChannels:channel_count
-                        error:nil];
-        if (success && [instance outputNumberOfChannels] == channel_count)
-            p_sys->b_preferred_channels_set = true;
-        else
-        {
-            /* Not critical, output channels layout will be Stereo */
-            msg_Warn(p_aout, "setPreferredOutputNumberOfChannels failed");
-        }
-    }
-}
-
-static void
-avas_resetPreferredNumberOfChannels(audio_output_t *p_aout)
-{
-    aout_sys_t *p_sys = p_aout->sys;
-    AVAudioSession *instance = p_sys->avInstance;
-
-    if (p_sys->b_preferred_channels_set)
-    {
-        [instance setPreferredOutputNumberOfChannels:2 error:nil];
-        p_sys->b_preferred_channels_set = false;
-    }
-}
-
-static int
-avas_GetPortType(audio_output_t *p_aout, enum port_type *pport_type)
-{
-    aout_sys_t * p_sys = p_aout->sys;
-    AVAudioSession *instance = p_sys->avInstance;
-    *pport_type = PORT_TYPE_DEFAULT;
-
-    long last_channel_count = 0;
-    for (AVAudioSessionPortDescription *out in [[instance currentRoute] outputs])
-    {
-        /* Choose the layout with the biggest number of channels or the HDMI
-         * one */
-
-        enum port_type port_type;
-        if ([out.portType isEqualToString: AVAudioSessionPortUSBAudio])
-            port_type = PORT_TYPE_USB;
-        else if ([out.portType isEqualToString: AVAudioSessionPortHDMI])
-            port_type = PORT_TYPE_HDMI;
-        else if ([out.portType isEqualToString: AVAudioSessionPortHeadphones])
-            port_type = PORT_TYPE_HEADPHONES;
-        else
-            port_type = PORT_TYPE_DEFAULT;
-
-        if (@available(iOS 15.0, tvOS 15.0, *)) {
-            p_sys->b_spatial_audio_supported = out.spatialAudioEnabled;
-        }
-
-        *pport_type = port_type;
-        if (port_type == PORT_TYPE_HDMI) /* Prefer HDMI */
-            break;
-    }
-
-    return VLC_SUCCESS;
-}
-
-struct API_AVAILABLE(ios(11.0))
-role2policy
-{
-    char role[sizeof("accessibility")];
-    AVAudioSessionRouteSharingPolicy policy;
-};
-
-static int API_AVAILABLE(ios(11.0))
-role2policy_cmp(const void *key, const void *val)
-{
-    const struct role2policy *entry = val;
-    return strcmp(key, entry->role);
-}
-
-static AVAudioSessionRouteSharingPolicy API_AVAILABLE(ios(11.0))
-GetRouteSharingPolicy(audio_output_t *p_aout)
-{
-#if __IPHONEOS_VERSION_MAX_ALLOWED < 130000
-    AVAudioSessionRouteSharingPolicy AVAudioSessionRouteSharingPolicyLongFormAudio =
-        AVAudioSessionRouteSharingPolicyLongForm;
-    AVAudioSessionRouteSharingPolicy AVAudioSessionRouteSharingPolicyLongFormVideo =
-        AVAudioSessionRouteSharingPolicyLongForm;
-#endif
-    /* LongFormAudio by default */
-    AVAudioSessionRouteSharingPolicy policy = AVAudioSessionRouteSharingPolicyLongFormAudio;
-    AVAudioSessionRouteSharingPolicy video_policy;
-#if !TARGET_OS_TV
-    if (@available(iOS 13.0, *))
-        video_policy = AVAudioSessionRouteSharingPolicyLongFormVideo;
-    else
-#endif
-        video_policy = AVAudioSessionRouteSharingPolicyLongFormAudio;
-
-    char *str = var_InheritString(p_aout, "role");
-    if (str != NULL)
-    {
-        const struct role2policy role_list[] =
-        {
-            { "accessibility", AVAudioSessionRouteSharingPolicyDefault },
-            { "animation",     AVAudioSessionRouteSharingPolicyDefault },
-            { "communication", AVAudioSessionRouteSharingPolicyDefault },
-            { "game",          AVAudioSessionRouteSharingPolicyLongFormAudio },
-            { "music",         AVAudioSessionRouteSharingPolicyLongFormAudio },
-            { "notification",  AVAudioSessionRouteSharingPolicyDefault },
-            { "production",    AVAudioSessionRouteSharingPolicyDefault },
-            { "test",          AVAudioSessionRouteSharingPolicyDefault },
-            { "video",         video_policy},
-        };
-
-        const struct role2policy *entry =
-            bsearch(str, role_list, ARRAY_SIZE(role_list),
-                    sizeof (*role_list), role2policy_cmp);
-        free(str);
-        if (entry != NULL)
-            policy = entry->policy;
-    }
-
-    return policy;
-}
-
-
-static int
-avas_SetActive(audio_output_t *p_aout, bool active, NSUInteger options)
-{
-    aout_sys_t * p_sys = p_aout->sys;
-    AVAudioSession *instance = p_sys->avInstance;
-    BOOL ret = false;
-    NSError *error = nil;
-
-    if (active)
-    {
-        if (@available(iOS 11.0, tvOS 11.0, *))
-        {
-            AVAudioSessionRouteSharingPolicy policy = GetRouteSharingPolicy(p_aout);
-
-            ret = [instance setCategory:AVAudioSessionCategoryPlayback
-                                   mode:AVAudioSessionModeMoviePlayback
-                     routeSharingPolicy:policy
-                                options:0
-                                  error:&error];
-        }
-        else
-        {
-            ret = [instance setCategory:AVAudioSessionCategoryPlayback
-                                  error:&error];
-            ret = ret && [instance setMode:AVAudioSessionModeMoviePlayback
-                                     error:&error];
-            /* Not AVAudioSessionRouteSharingPolicy on older devices */
-        }
-        if (@available(iOS 15.0, tvOS 15.0, *)) {
-            ret = ret && [instance setSupportsMultichannelContent:p_sys->b_spatial_audio_supported error:&error];
-        }
-        ret = ret && [instance setActive:YES withOptions:options error:&error];
-        if (ret)
-            [[SessionManager sharedInstance] addAoutInstance: p_sys->aoutWrapper];
-    } else {
-        NSInteger numberOfRegisteredInstances = [[SessionManager sharedInstance] removeAoutInstance: p_sys->aoutWrapper];
-        if (numberOfRegisteredInstances == 0) {
-            ret = [instance setActive:NO withOptions:options error:&error];
-        } else {
-            ret = true;
-        }
-    }
-
-    if (!ret)
-    {
-        msg_Err(p_aout, "AVAudioSession playback change failed: %s(%d)",
-                error.domain.UTF8String, (int)error.code);
-        return VLC_EGENERIC;
-    }
-
-    return VLC_SUCCESS;
-}
-
 #pragma mark -
 #pragma mark actual playback
 
@@ -472,17 +194,17 @@ Pause (audio_output_t *p_aout, bool pause, vlc_tick_t date)
         err = AudioOutputUnitStop(p_sys->au_unit);
         if (err != noErr)
             ca_LogErr("AudioOutputUnitStop failed");
-        avas_SetActive(p_aout, false, 0);
+        avas_SetActive(p_aout, p_sys->avInstance, false, 0);
     }
     else
     {
-        if (avas_SetActive(p_aout, true, 0) == VLC_SUCCESS)
+        if (avas_SetActive(p_aout, p_sys->avInstance, true, 0) == VLC_SUCCESS)
         {
             err = AudioOutputUnitStart(p_sys->au_unit);
             if (err != noErr)
             {
                 ca_LogErr("AudioOutputUnitStart failed");
-                avas_SetActive(p_aout, false, 0);
+                avas_SetActive(p_aout, p_sys->avInstance, false, 0);
                 /* Do not un-pause, the Render Callback won't run, and next call
                  * of ca_Play will deadlock */
                 return;
@@ -536,9 +258,7 @@ Stop(audio_output_t *p_aout)
     if (err != noErr)
         ca_LogWarn("AudioComponentInstanceDispose failed");
 
-    avas_resetPreferredNumberOfChannels(p_aout);
-
-    avas_SetActive(p_aout, false,
+    avas_SetActive(p_aout, p_sys->avInstance, false,
                    AVAudioSessionSetActiveOptionNotifyOthersOnDeactivation);
 }
 
@@ -571,50 +291,26 @@ Start(audio_output_t *p_aout, audio_sample_format_t *restrict fmt)
                            selector:@selector(handleInterruption:)
                                name:AVAudioSessionInterruptionNotification
                              object:nil];
-    if (@available(iOS 15.0, tvOS 15.0, *)) {
-        [notificationCenter addObserver:p_sys->aoutWrapper
-                               selector:@selector(handleSpatialCapabilityChange:)
-                                   name:AVAudioSessionSpatialPlaybackCapabilitiesChangedNotification
-                                 object:nil];
-    }
 
     /* Activate the AVAudioSession */
-    if (avas_SetActive(p_aout, true, 0) != VLC_SUCCESS)
+    if (avas_SetActive(p_aout, p_sys->avInstance, true, 0) != VLC_SUCCESS)
     {
         [[NSNotificationCenter defaultCenter] removeObserver:p_sys->aoutWrapper];
         return VLC_EGENERIC;
     }
 
-    /* Set the preferred number of channels, then fetch the channel layout that
-     * should correspond to this number */
-    avas_setPreferredNumberOfChannels(p_aout, fmt);
-
-    BOOL success = [p_sys->avInstance setPreferredSampleRate:fmt->i_rate error:nil];
-    if (!success)
-    {
-        /* Not critical, we can use any sample rates */
-        msg_Dbg(p_aout, "failed to set preferred sample rate");
-    }
+    avas_PrepareFormat(p_aout, p_sys->avInstance, fmt, false);
 
     enum port_type port_type;
-    int ret = avas_GetPortType(p_aout, &port_type);
+    int ret = avas_GetPortType(p_aout, p_sys->avInstance, &port_type);
     if (ret != VLC_SUCCESS)
         goto error;
 
-    msg_Dbg(p_aout, "Output on %s, channel count: %ld, spatialAudioEnabled %i",
+    msg_Dbg(p_aout, "Output on %s, channel count: %ld",
             port_type == PORT_TYPE_HDMI ? "HDMI" :
             port_type == PORT_TYPE_USB ? "USB" :
             port_type == PORT_TYPE_HEADPHONES ? "Headphones" : "Default",
-            (long) [p_sys->avInstance outputNumberOfChannels],
-            p_sys->b_spatial_audio_supported);
-
-    if (!p_sys->b_preferred_channels_set && fmt->i_channels > 2)
-    {
-        /* Ask the core to downmix to stereo if the preferred number of
-         * channels can't be set. */
-        fmt->i_physical_channels = AOUT_CHANS_STEREO;
-        aout_FormatPrepare(fmt);
-    }
+            (long) [p_sys->avInstance outputNumberOfChannels]);
 
     p_aout->current_sink_info.headphones = port_type == PORT_TYPE_HEADPHONES;
 
@@ -658,8 +354,7 @@ Start(audio_output_t *p_aout, audio_sample_format_t *restrict fmt)
 error:
     if (p_sys->au_unit != NULL)
         AudioComponentInstanceDispose(p_sys->au_unit);
-    avas_resetPreferredNumberOfChannels(p_aout);
-    avas_SetActive(p_aout, false,
+    avas_SetActive(p_aout, p_sys->avInstance, false,
                    AVAudioSessionSetActiveOptionNotifyOthersOnDeactivation);
     [[NSNotificationCenter defaultCenter] removeObserver:p_sys->aoutWrapper];
     msg_Err(p_aout, "opening AudioUnit output failed");
@@ -730,8 +425,6 @@ Open(vlc_object_t *obj)
     }
 
     sys->b_muted = false;
-    sys->b_preferred_channels_set = false;
-    sys->b_spatial_audio_supported = false;
     sys->au_dev = var_InheritBool(aout, "spdif") ? AU_DEV_ENCODED : AU_DEV_PCM;
     aout->start = Start;
     aout->stop = Stop;
@@ -752,7 +445,7 @@ Open(vlc_object_t *obj)
 vlc_module_begin ()
     set_shortname("audiounit_ios")
     set_description("AudioUnit output for iOS")
-    set_capability("audio output", 101)
+    set_capability("audio output", 99)
     set_subcategory(SUBCAT_AUDIO_AOUT)
     add_sw_gain()
     set_callbacks(Open, Close)


=====================================
modules/audio_output/auhal.c → modules/audio_output/apple/auhal.c
=====================================


=====================================
modules/audio_output/apple/avaudiosession_common.h
=====================================
@@ -0,0 +1,41 @@
+/*****************************************************************************
+ * avaudiosession_common.h: AVAudioSession common code for iOS aouts
+ *****************************************************************************
+ * Copyright (C) 2012 - 2024 VLC authors and VideoLAN
+ *
+ * Authors: Felix Paul Kühne <fkuehne at videolan dot org>
+ *
+ * This program 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.
+ *
+ * This program 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 this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
+ *****************************************************************************/
+
+enum port_type
+{
+    PORT_TYPE_DEFAULT,
+    PORT_TYPE_USB,
+    PORT_TYPE_HDMI,
+    PORT_TYPE_HEADPHONES
+};
+
+void
+avas_PrepareFormat(audio_output_t *p_aout, AVAudioSession *instance,
+                   audio_sample_format_t *fmt, bool spatial_audio);
+
+int
+avas_GetPortType(audio_output_t *p_aout, AVAudioSession *instance,
+                 enum port_type *pport_type);
+
+int
+avas_SetActive(audio_output_t *p_aout, AVAudioSession *instance, bool active,
+               NSUInteger options);


=====================================
modules/audio_output/apple/avaudiosession_common.m
=====================================
@@ -0,0 +1,227 @@
+/*****************************************************************************
+ * avaudiosession_common.m: AVAudioSession common code for iOS aouts
+ *****************************************************************************
+ * Copyright (C) 2012 - 2024 VLC authors and VideoLAN
+ *
+ * Authors: Felix Paul Kühne <fkuehne at videolan dot org>
+ *
+ * This program 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.
+ *
+ * This program 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 this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
+ *****************************************************************************/
+
+#import "config.h"
+
+#import <vlc_common.h>
+#import <vlc_aout.h>
+#import <vlc_atomic.h>
+
+#import <AVFoundation/AVFoundation.h>
+#import "avaudiosession_common.h"
+
+void
+avas_PrepareFormat(audio_output_t *p_aout, AVAudioSession *instance,
+                   audio_sample_format_t *fmt, bool spatial_audio)
+{
+    if (aout_BitsPerSample(fmt->i_format) == 0)
+        return; /* Don't touch the number of channels for passthrough */
+
+    NSInteger max_channel_count = [instance maximumOutputNumberOfChannels];
+    unsigned channel_count = aout_FormatNbChannels(fmt);
+
+    /* Increase the preferred number of output channels if possible */
+    if (channel_count > max_channel_count)
+    {
+        msg_Warn(p_aout, "Requested channel count %u not fully supported, "
+                 "downmixing to %ld\n", channel_count, (long)max_channel_count);
+        channel_count = max_channel_count;
+    }
+
+    NSError *error = nil;
+    BOOL success = [instance setPreferredOutputNumberOfChannels:channel_count
+                                                          error:&error];
+    if (!success || [instance outputNumberOfChannels] != channel_count)
+    {
+        /* Not critical, output channels layout will be Stereo */
+        msg_Warn(p_aout, "setPreferredOutputNumberOfChannels failed %s(%d)",
+                 !success ? error.domain.UTF8String : "",
+                 !success ? (int)error.code : 0);
+        channel_count = 2;
+    }
+
+    if (spatial_audio)
+    {
+        if (@available(iOS 15.0, tvOS 15.0, *))
+        {
+            /* Not mandatory, SpatialAudio can work without it. It just signals to
+             * the user that he is playing spatial content */
+            [instance setSupportsMultichannelContent:aout_FormatNbChannels(fmt) > 2
+                                               error:nil];
+        }
+    }
+    else if (channel_count == 2 && aout_FormatNbChannels(fmt) > 2)
+    {
+        /* Ask the core to downmix to stereo if the preferred number of
+         * channels can't be set. */
+        fmt->i_physical_channels = AOUT_CHANS_STEREO;
+        aout_FormatPrepare(fmt);
+    }
+
+    success = [instance setPreferredSampleRate:fmt->i_rate error:&error];
+    if (!success)
+    {
+        /* Not critical, we can use any sample rates */
+        msg_Dbg(p_aout, "setPreferredSampleRate failed %s(%d)",
+                error.domain.UTF8String, (int)error.code);
+    }
+}
+
+int
+avas_GetPortType(audio_output_t *p_aout, AVAudioSession *instance,
+                 enum port_type *pport_type)
+{
+    (void) p_aout;
+    *pport_type = PORT_TYPE_DEFAULT;
+
+    long last_channel_count = 0;
+    for (AVAudioSessionPortDescription *out in [[instance currentRoute] outputs])
+    {
+        /* Choose the layout with the biggest number of channels or the HDMI
+         * one */
+
+        enum port_type port_type;
+        if ([out.portType isEqualToString: AVAudioSessionPortUSBAudio])
+            port_type = PORT_TYPE_USB;
+        else if ([out.portType isEqualToString: AVAudioSessionPortHDMI])
+            port_type = PORT_TYPE_HDMI;
+        else if ([out.portType isEqualToString: AVAudioSessionPortHeadphones])
+            port_type = PORT_TYPE_HEADPHONES;
+        else
+            port_type = PORT_TYPE_DEFAULT;
+
+        *pport_type = port_type;
+        if (port_type == PORT_TYPE_HDMI) /* Prefer HDMI */
+            break;
+    }
+
+    return VLC_SUCCESS;
+}
+
+struct API_AVAILABLE(ios(11.0))
+role2policy
+{
+    char role[sizeof("accessibility")];
+    AVAudioSessionRouteSharingPolicy policy;
+};
+
+static int API_AVAILABLE(ios(11.0))
+role2policy_cmp(const void *key, const void *val)
+{
+    const struct role2policy *entry = val;
+    return strcmp(key, entry->role);
+}
+
+static AVAudioSessionRouteSharingPolicy API_AVAILABLE(ios(11.0))
+GetRouteSharingPolicy(audio_output_t *p_aout)
+{
+#if __IPHONEOS_VERSION_MAX_ALLOWED < 130000
+    AVAudioSessionRouteSharingPolicy AVAudioSessionRouteSharingPolicyLongFormAudio =
+        AVAudioSessionRouteSharingPolicyLongForm;
+    AVAudioSessionRouteSharingPolicy AVAudioSessionRouteSharingPolicyLongFormVideo =
+        AVAudioSessionRouteSharingPolicyLongForm;
+#endif
+    /* LongFormAudio by default */
+    AVAudioSessionRouteSharingPolicy policy = AVAudioSessionRouteSharingPolicyLongFormAudio;
+    AVAudioSessionRouteSharingPolicy video_policy;
+#if !TARGET_OS_TV
+    if (@available(iOS 13.0, *))
+        video_policy = AVAudioSessionRouteSharingPolicyLongFormVideo;
+    else
+#endif
+        video_policy = AVAudioSessionRouteSharingPolicyLongFormAudio;
+
+    char *str = var_InheritString(p_aout, "role");
+    if (str != NULL)
+    {
+        const struct role2policy role_list[] =
+        {
+            { "accessibility", AVAudioSessionRouteSharingPolicyDefault },
+            { "animation",     AVAudioSessionRouteSharingPolicyDefault },
+            { "communication", AVAudioSessionRouteSharingPolicyDefault },
+            { "game",          AVAudioSessionRouteSharingPolicyLongFormAudio },
+            { "music",         AVAudioSessionRouteSharingPolicyLongFormAudio },
+            { "notification",  AVAudioSessionRouteSharingPolicyDefault },
+            { "production",    AVAudioSessionRouteSharingPolicyDefault },
+            { "test",          AVAudioSessionRouteSharingPolicyDefault },
+            { "video",         video_policy},
+        };
+
+        const struct role2policy *entry =
+            bsearch(str, role_list, ARRAY_SIZE(role_list),
+                    sizeof (*role_list), role2policy_cmp);
+        free(str);
+        if (entry != NULL)
+            policy = entry->policy;
+    }
+
+    return policy;
+}
+
+int
+avas_SetActive(audio_output_t *p_aout, AVAudioSession *instance, bool active,
+               NSUInteger options)
+{
+    static vlc_atomic_rc_t active_rc = VLC_STATIC_RC;
+
+    BOOL ret = false;
+    NSError *error = nil;
+
+    if (active)
+    {
+        if (@available(iOS 11.0, tvOS 11.0, *))
+        {
+            AVAudioSessionRouteSharingPolicy policy = GetRouteSharingPolicy(p_aout);
+
+            ret = [instance setCategory:AVAudioSessionCategoryPlayback
+                                   mode:AVAudioSessionModeMoviePlayback
+                     routeSharingPolicy:policy
+                                options:0
+                                  error:&error];
+        }
+        else
+        {
+            ret = [instance setCategory:AVAudioSessionCategoryPlayback
+                                  error:&error];
+            ret = ret && [instance setMode:AVAudioSessionModeMoviePlayback
+                                     error:&error];
+            /* Not AVAudioSessionRouteSharingPolicy on older devices */
+        }
+        ret = ret && [instance setActive:YES withOptions:options error:&error];
+        if (ret)
+            vlc_atomic_rc_inc(&active_rc);
+    } else {
+        if (vlc_atomic_rc_dec(&active_rc))
+            ret = [instance setActive:NO withOptions:options error:&error];
+        else
+            ret = true;
+    }
+
+    if (!ret)
+    {
+        msg_Err(p_aout, "AVAudioSession playback change failed: %s(%d)",
+                error.domain.UTF8String, (int)error.code);
+        return VLC_EGENERIC;
+    }
+
+    return VLC_SUCCESS;
+}


=====================================
modules/audio_output/apple/avsamplebuffer.m
=====================================
@@ -0,0 +1,559 @@
+/*****************************************************************************
+ * avsamplebuffer.m: AVSampleBufferRender plugin for iOS and macOS
+ *****************************************************************************
+ * Copyright (C) 2024 VLC authors, VideoLAN and VideoLABS
+ *
+ * This program 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.
+ *
+ * This program 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 this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
+ *****************************************************************************/
+
+#import "config.h"
+
+#import <TargetConditionals.h>
+#import <Foundation/Foundation.h>
+#import <AVFoundation/AVFoundation.h>
+
+#import <vlc_common.h>
+#import <vlc_plugin.h>
+#import <vlc_aout.h>
+
+#if TARGET_OS_IPHONE || TARGET_OS_TV
+#define HAS_AVAUDIOSESSION
+#import "avaudiosession_common.h"
+#endif
+
+#import "channel_layout.h"
+
+// for (void)setRate:(float)rate time:(CMTime)time atHostTime:(CMTime)hostTime
+#define MIN_MACOS 11.3
+#define MIN_IOS 14.5
+#define MIN_TVOS 14.5
+
+#pragma mark Private
+
+API_AVAILABLE(macos(MIN_MACOS), ios(MIN_IOS), tvos(MIN_TVOS))
+ at interface VLCAVSample : NSObject
+{
+    audio_output_t *_aout;
+
+    CMAudioFormatDescriptionRef _fmtDesc;
+    AVSampleBufferAudioRenderer *_renderer;
+    AVSampleBufferRenderSynchronizer *_sync;
+    id _observer;
+    dispatch_queue_t _dataQueue;
+    dispatch_queue_t _timeQueue;
+    size_t _bytesPerFrame;
+
+    vlc_mutex_t _bufferLock;
+    vlc_cond_t _bufferWait;
+
+    block_t *_outChain;
+    block_t **_outChainLast;
+
+    int64_t _ptsSamples;
+    unsigned _sampleRate;
+    BOOL _stopped;
+}
+ at end
+
+ at implementation VLCAVSample
+
+- (id)init:(audio_output_t*)aout
+{
+    _aout = aout;
+    _dataQueue = dispatch_queue_create("VLC AVSampleBuffer data queue", DISPATCH_QUEUE_SERIAL);
+    if (_dataQueue == nil)
+        return nil;
+
+    _timeQueue = dispatch_queue_create("VLC AVSampleBuffer time queue", DISPATCH_QUEUE_SERIAL);
+    if (_timeQueue == nil)
+        return nil;
+
+    vlc_mutex_init(&_bufferLock);
+    vlc_cond_init(&_bufferWait);
+
+    _outChain = NULL;
+    _outChainLast = &_outChain;
+
+    self = [super init];
+    if (self == nil)
+        return nil;
+
+    return self;
+}
+
+- (void)dealloc
+{
+    block_ChainRelease(_outChain);
+}
+
+- (void)clearOutChain
+{
+    block_ChainRelease(_outChain);
+    _outChain = NULL;
+    _outChainLast = &_outChain;
+}
+
+static void
+customBlock_Free(void *refcon, void *doomedMemoryBlock, size_t sizeInBytes)
+{
+    block_t *block = refcon;
+
+    assert(block->i_buffer == sizeInBytes);
+    block_Release(block);
+
+    (void) doomedMemoryBlock;
+    (void) sizeInBytes;
+}
+
+- (CMSampleBufferRef)wrapBuffer:(block_t **)pblock
+{
+    // This function take the block ownership
+    block_t *block = *pblock;
+    *pblock = NULL;
+
+    const CMBlockBufferCustomBlockSource blockSource = {
+        .version = kCMBlockBufferCustomBlockSourceVersion,
+        .FreeBlock = customBlock_Free,
+        .refCon = block,
+    };
+
+    OSStatus status;
+    CMBlockBufferRef blockBuf;
+    status = CMBlockBufferCreateWithMemoryBlock(kCFAllocatorDefault,
+                                                block->p_buffer,  // memoryBlock
+                                                block->i_buffer,  // blockLength
+                                                NULL,             // blockAllocator
+                                                &blockSource,     // customBlockSource
+                                                0,                // offsetToData
+                                                block->i_buffer,  // dataLength
+                                                0,                // flags
+                                                &blockBuf);
+    if (status != noErr)
+    {
+        msg_Err(_aout, "CMBlockBufferRef creation failure %li", (long)status);
+        return nil;
+    }
+
+    const CMSampleTimingInfo timeInfo = {
+        .duration = kCMTimeInvalid,
+        .presentationTimeStamp = CMTimeMake(_ptsSamples, _sampleRate),
+        .decodeTimeStamp = kCMTimeInvalid,
+    };
+
+    CMSampleBufferRef sampleBuf;
+    status = CMSampleBufferCreateReady(kCFAllocatorDefault,
+                                       blockBuf,             // dataBuffer
+                                       _fmtDesc,             // formatDescription
+                                       block->i_nb_samples,  // numSamples
+                                       1,                    // numSampleTimingEntries
+                                       &timeInfo,            // sampleTimingArray
+                                       1,                    // numSampleSizeEntries
+                                       &_bytesPerFrame,      // sampleSizeArray
+                                       &sampleBuf);
+    CFRelease(blockBuf);
+
+    if (status != noErr)
+    {
+        msg_Warn(_aout, "CMSampleBufferRef creation failure %li", (long)status);
+        return nil;
+    }
+
+    return sampleBuf;
+}
+
+- (void)selectDevice:(const char *)name
+{
+}
+
+- (void)setMute:(BOOL)muted
+{
+    _renderer.muted = muted;
+    aout_MuteReport(_aout, muted);
+}
+
+- (void)setVolume:(float)volume
+{
+    _renderer.volume = volume * volume * volume;
+    aout_VolumeReport(_aout, volume);
+}
+
++ (vlc_tick_t)CMTimeTotick:(CMTime) timestamp
+{
+    CMTime scaled = CMTimeConvertScale(
+            timestamp, CLOCK_FREQ,
+            kCMTimeRoundingMethod_Default);
+
+    return scaled.value;
+}
+
+- (void)flush
+{
+    if (_ptsSamples >= 0)
+        [self stopSyncRenderer];
+
+    _ptsSamples = -1;
+}
+
+- (void)pause:(BOOL)pause date:(vlc_tick_t)date
+{
+    (void) date;
+
+    if (_ptsSamples >= 0)
+        _sync.rate = pause ? 0.0f : 1.0f;
+}
+
+- (void)whenTimeObserved:(CMTime) time
+{
+    if (time.value == 0)
+        return;
+    vlc_tick_t system_now = vlc_tick_now();
+    vlc_tick_t pos_ticks = [VLCAVSample CMTimeTotick:time];
+
+    aout_TimingReport(_aout, system_now, pos_ticks);
+}
+
+- (void)whenDataReady
+{
+    vlc_mutex_lock(&_bufferLock);
+
+    while (_renderer.readyForMoreMediaData)
+    {
+        while (!_stopped && _outChain == NULL)
+            vlc_cond_wait(&_bufferWait, &_bufferLock);
+
+        if (_stopped)
+        {
+            vlc_mutex_unlock(&_bufferLock);
+            return;
+        }
+
+        block_t *block = _outChain;
+        _outChain = _outChain->p_next;
+        if (_outChain == NULL)
+            _outChainLast = &_outChain;
+
+        CMSampleBufferRef buffer = [self wrapBuffer:&block];
+        _ptsSamples += CMSampleBufferGetNumSamples(buffer);
+
+        [_renderer enqueueSampleBuffer:buffer];
+
+        CFRelease(buffer);
+    }
+
+    vlc_mutex_unlock(&_bufferLock);
+}
+
+- (void)play:(block_t *)block date:(vlc_tick_t)date
+{
+    vlc_mutex_lock(&_bufferLock);
+
+    if (_ptsSamples == -1)
+    {
+        __weak typeof(self) weakSelf = self;
+        [_renderer requestMediaDataWhenReadyOnQueue:_dataQueue usingBlock:^{
+            [weakSelf whenDataReady];
+        }];
+
+        const CMTime interval = CMTimeMake(CLOCK_FREQ, CLOCK_FREQ);
+        _observer = [_sync addPeriodicTimeObserverForInterval:interval
+                                                        queue:_timeQueue
+                                                   usingBlock:^ (CMTime time){
+            [weakSelf whenTimeObserved:time];
+        }];
+
+        _ptsSamples = 0;
+        vlc_tick_t delta = date - vlc_tick_now();
+        CMTime hostTime = CMTimeAdd(CMClockGetTime(CMClockGetHostTimeClock()),
+                                    CMTimeMake(delta, CLOCK_FREQ));
+        CMTime time = CMTimeMake(_ptsSamples, _sampleRate);
+
+        _sync.delaysRateChangeUntilHasSufficientMediaData = NO;
+        [_sync setRate:1.0f time:time atHostTime:hostTime];
+    }
+
+    block_ChainLastAppend(&_outChainLast, block);
+
+    vlc_cond_signal(&_bufferWait);
+    vlc_mutex_unlock(&_bufferLock);
+}
+
+- (void)stopSyncRenderer
+{
+    _sync.rate = 0.0f;
+
+    [_sync removeTimeObserver:_observer];
+    [_renderer stopRequestingMediaData];
+    [_renderer flush];
+
+    [self clearOutChain];
+}
+
+- (void)stop
+{
+    NSNotificationCenter *notifCenter = [NSNotificationCenter defaultCenter];
+
+    vlc_mutex_lock(&_bufferLock);
+    _stopped = YES;
+    vlc_cond_signal(&_bufferWait);
+    vlc_mutex_unlock(&_bufferLock);
+
+    if (_ptsSamples > 0)
+        [self stopSyncRenderer];
+
+    [_sync removeRenderer:_renderer atTime:kCMTimeInvalid completionHandler:nil];
+
+#ifdef HAS_AVAUDIOSESSION
+    avas_SetActive(_aout, [AVAudioSession sharedInstance], false,
+                   AVAudioSessionSetActiveOptionNotifyOthersOnDeactivation);
+#endif
+
+    CFRelease(_fmtDesc);
+
+    [notifCenter removeObserver:self];
+}
+
+- (void)flushedAutomatically:(NSNotification *)notification
+{
+    msg_Warn(_aout, "flushedAutomatically");
+    aout_RestartRequest(_aout, false);
+}
+
+- (void)outputConfigurationChanged:(NSNotification *)notification
+{
+    msg_Warn(_aout, "outputConfigurationChanged");
+    aout_RestartRequest(_aout, false);
+}
+
+- (BOOL)start:(audio_sample_format_t *)fmt
+{
+    if (aout_BitsPerSample(fmt->i_format) == 0)
+        return NO; /* Can handle PT */
+
+    NSNotificationCenter *notifCenter = [NSNotificationCenter defaultCenter];
+
+    fmt->i_format = VLC_CODEC_FL32;
+
+#ifdef HAS_AVAUDIOSESSION
+    AVAudioSession *instance = [AVAudioSession sharedInstance];
+    if (avas_SetActive(_aout, instance, true, 0) != VLC_SUCCESS)
+        return NO;
+    avas_PrepareFormat(_aout, instance, fmt, true);
+
+    enum port_type port_type;
+    if (avas_GetPortType(_aout, instance, &port_type) == VLC_SUCCESS)
+    {
+        msg_Dbg(_aout, "Output on %s, channel count: %u",
+                port_type == PORT_TYPE_HDMI ? "HDMI" :
+                port_type == PORT_TYPE_USB ? "USB" :
+                port_type == PORT_TYPE_HEADPHONES ? "Headphones" : "Default",
+                aout_FormatNbChannels(fmt));
+
+        _aout->current_sink_info.headphones = port_type == PORT_TYPE_HEADPHONES;
+    }
+#endif
+
+    AudioChannelLayout *inlayout_buf = NULL;
+    size_t inlayout_size = 0;
+    int err = channel_layout_MapFromVLC(_aout, fmt, &inlayout_buf,
+                                        &inlayout_size);
+    if (err != VLC_SUCCESS)
+        goto error_avas;
+
+    AudioStreamBasicDescription desc = {
+        .mSampleRate = fmt->i_rate,
+        .mFormatID = kAudioFormatLinearPCM,
+        .mFormatFlags = kAudioFormatFlagsNativeFloatPacked,
+        .mChannelsPerFrame = aout_FormatNbChannels(fmt),
+        .mFramesPerPacket = 1,
+        .mBitsPerChannel = 32,
+    };
+
+    desc.mBytesPerFrame = desc.mBitsPerChannel * desc.mChannelsPerFrame / 8;
+    desc.mBytesPerPacket = desc.mBytesPerFrame * desc.mFramesPerPacket;
+
+    OSStatus status =
+        CMAudioFormatDescriptionCreate(kCFAllocatorDefault,
+                                       &desc,
+                                       inlayout_size,
+                                       inlayout_buf,
+                                       0,
+                                       nil,
+                                       nil,
+                                       &_fmtDesc);
+    free(inlayout_buf);
+    if (status != noErr)
+    {
+        msg_Warn(_aout, "CMAudioFormatDescriptionRef creation failure %li", (long)status);
+        goto error_avas;
+    }
+
+    _renderer = [[AVSampleBufferAudioRenderer alloc] init];
+    if (_renderer == nil)
+        goto error;
+
+    _sync = [[AVSampleBufferRenderSynchronizer alloc] init];
+    if (_sync == nil)
+    {
+        _renderer = nil;
+        goto error;
+    }
+
+    [_sync addRenderer:_renderer];
+
+    _stopped = NO;
+
+    _ptsSamples = -1;
+    _sampleRate = fmt->i_rate;
+    _bytesPerFrame = desc.mBytesPerFrame;
+
+    [notifCenter addObserver:self
+                    selector:@selector(flushedAutomatically:)
+                        name:AVSampleBufferAudioRendererWasFlushedAutomaticallyNotification
+                      object:nil];
+    if (@available(macOS 12.0, iOS 15.0, tvOS 15.0, *))
+    {
+        [notifCenter addObserver:self
+                        selector:@selector(outputConfigurationChanged:)
+                            name:AVSampleBufferAudioRendererOutputConfigurationDidChangeNotification
+                          object:nil];
+    }
+
+    return YES;
+error:
+    CFRelease(_fmtDesc);
+error_avas:
+#ifdef HAS_AVAUDIOSESSION
+    avas_SetActive(_aout, instance, false,
+                   AVAudioSessionSetActiveOptionNotifyOthersOnDeactivation);
+#endif
+    return NO;
+}
+
+ at end
+
+static int API_AVAILABLE(macos(MIN_MACOS), ios(MIN_IOS), tvos(MIN_TVOS))
+DeviceSelect(audio_output_t *aout, const char *name)
+{
+    VLCAVSample *sys = (__bridge VLCAVSample*)aout->sys;
+
+    [sys selectDevice:name];
+
+    return VLC_SUCCESS;
+}
+
+static int API_AVAILABLE(macos(MIN_MACOS), ios(MIN_IOS), tvos(MIN_TVOS))
+MuteSet(audio_output_t *aout, bool mute)
+{
+    VLCAVSample *sys = (__bridge VLCAVSample*)aout->sys;
+
+    [sys setMute:mute];
+
+    return VLC_SUCCESS;
+}
+
+static int API_AVAILABLE(macos(MIN_MACOS), ios(MIN_IOS), tvos(MIN_TVOS))
+VolumeSet(audio_output_t *aout, float volume)
+{
+    VLCAVSample *sys = (__bridge VLCAVSample*)aout->sys;
+
+    [sys setVolume:volume];
+
+    return VLC_SUCCESS;
+}
+
+static void API_AVAILABLE(macos(MIN_MACOS), ios(MIN_IOS), tvos(MIN_TVOS))
+Flush(audio_output_t *aout)
+{
+    VLCAVSample *sys = (__bridge VLCAVSample*)aout->sys;
+
+    [sys flush];
+}
+
+static void API_AVAILABLE(macos(MIN_MACOS), ios(MIN_IOS), tvos(MIN_TVOS))
+Pause(audio_output_t *aout, bool pause, vlc_tick_t date)
+{
+    VLCAVSample *sys = (__bridge VLCAVSample*)aout->sys;
+
+    [sys pause:pause date:date];
+}
+
+static void API_AVAILABLE(macos(MIN_MACOS), ios(MIN_IOS), tvos(MIN_TVOS))
+Play(audio_output_t *aout, block_t *block, vlc_tick_t date)
+{
+    VLCAVSample *sys = (__bridge VLCAVSample*)aout->sys;
+
+    [sys play:block date:date];
+}
+
+static void API_AVAILABLE(macos(MIN_MACOS), ios(MIN_IOS), tvos(MIN_TVOS))
+Stop(audio_output_t *aout)
+{
+    VLCAVSample *sys = (__bridge VLCAVSample*)aout->sys;
+
+    [sys stop];
+}
+
+static int API_AVAILABLE(macos(MIN_MACOS), ios(MIN_IOS), tvos(MIN_TVOS))
+Start(audio_output_t *aout, audio_sample_format_t *restrict fmt)
+{
+    VLCAVSample *sys = (__bridge VLCAVSample*)aout->sys;
+
+    return [sys start:fmt] ? VLC_SUCCESS : VLC_EGENERIC;
+}
+
+static void
+Close(vlc_object_t *obj)
+{
+    if (@available(macOS MIN_MACOS, iOS MIN_IOS, tvOS MIN_TVOS, *))
+    {
+        audio_output_t *aout = (audio_output_t *)obj;
+        /* Transfer ownership back from VLC to ARC so that it can be released. */
+        VLCAVSample *sys = (__bridge_transfer VLCAVSample*)aout->sys;
+        (void) sys;
+    }
+}
+
+static int
+Open(vlc_object_t *obj)
+{
+    audio_output_t *aout = (audio_output_t *)obj;
+
+    if (@available(macOS MIN_MACOS, iOS MIN_IOS, tvOS MIN_TVOS, *))
+    {
+        aout->sys = (__bridge_retained void*) [[VLCAVSample alloc] init:aout];
+        if (aout->sys == nil)
+            return VLC_EGENERIC;
+
+        aout->start = Start;
+        aout->stop = Stop;
+        aout->play = Play;
+        aout->pause = Pause;
+        aout->flush = Flush;
+        aout->volume_set = VolumeSet;
+        aout->mute_set = MuteSet;
+        aout->device_select = DeviceSelect;
+
+        return VLC_SUCCESS;
+    }
+    return VLC_EGENERIC;
+}
+
+vlc_module_begin ()
+    set_shortname("avsample")
+    set_description(N_("AVSampleBufferAudioRenderer output"))
+    set_capability("audio output", 100)
+    set_subcategory(SUBCAT_AUDIO_AOUT)
+    set_callbacks(Open, Close)
+vlc_module_end ()


=====================================
modules/audio_output/apple/channel_layout.c
=====================================
@@ -0,0 +1,104 @@
+/*****************************************************************************
+ * channel_layout.c: Common Channel Layout code for iOS and macOS
+ *****************************************************************************
+ * Copyright (C) 2024 VLC authors and VideoLAN
+ *
+ * This program 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.
+ *
+ * This program 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 this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
+ *****************************************************************************/
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <vlc_common.h>
+#include <vlc_aout.h>
+#include "channel_layout.h"
+
+#include <stdckdint.h>
+#include <CoreAudio/CoreAudioTypes.h>
+
+static AudioChannelLabel
+VlcChanToAudioChannelLabel(unsigned chan, bool swap_rear_surround)
+{
+    switch (chan)
+    {
+        case AOUT_CHAN_LEFT:
+            return kAudioChannelLabel_Left;
+        case AOUT_CHAN_RIGHT:
+            return kAudioChannelLabel_Right;
+        case AOUT_CHAN_CENTER:
+            return kAudioChannelLabel_Center;
+        case AOUT_CHAN_LFE:
+            return kAudioChannelLabel_LFEScreen;
+        case AOUT_CHAN_REARLEFT:
+            return swap_rear_surround ? kAudioChannelLabel_RearSurroundLeft
+                                      : kAudioChannelLabel_LeftSurround;
+        case AOUT_CHAN_REARRIGHT:
+            return swap_rear_surround ? kAudioChannelLabel_RearSurroundRight
+                                      : kAudioChannelLabel_RightSurround;
+        case AOUT_CHAN_MIDDLELEFT:
+            return swap_rear_surround ? kAudioChannelLabel_LeftSurround
+                                      : kAudioChannelLabel_RearSurroundLeft;
+        case AOUT_CHAN_MIDDLERIGHT:
+            return swap_rear_surround ? kAudioChannelLabel_RightSurround
+                                      : kAudioChannelLabel_RearSurroundRight;
+        case AOUT_CHAN_REARCENTER:
+            return kAudioChannelLabel_CenterSurround;
+        default:
+            vlc_assert_unreachable();
+    }
+}
+
+int
+channel_layout_MapFromVLC(audio_output_t *p_aout, const audio_sample_format_t *fmt,
+                          AudioChannelLayout **inlayoutp, size_t *inlayout_size)
+{
+    unsigned channels = aout_FormatNbChannels(fmt);
+
+    size_t size;
+    if (ckd_mul(&size, channels, sizeof(AudioChannelDescription)) ||
+        ckd_add(&size, size, sizeof(AudioChannelLayout)))
+        return VLC_ENOMEM;
+    AudioChannelLayout *inlayout = malloc(size);
+    if (inlayout == NULL)
+        return VLC_ENOMEM;
+
+    *inlayoutp = inlayout;
+    *inlayout_size = size;
+    inlayout->mChannelLayoutTag = kAudioChannelLayoutTag_UseChannelDescriptions;
+    inlayout->mNumberChannelDescriptions = aout_FormatNbChannels(fmt);
+
+    bool swap_rear_surround = (fmt->i_physical_channels & AOUT_CHANS_7_0) == AOUT_CHANS_7_0;
+    if (swap_rear_surround)
+        msg_Dbg(p_aout, "swapping Surround and RearSurround channels "
+                "for 7.1 Rear Surround");
+    unsigned chan_idx = 0;
+    for (unsigned i = 0; i < AOUT_CHAN_MAX; ++i)
+    {
+        unsigned vlcchan = pi_vlc_chan_order_wg4[i];
+        if ((vlcchan & fmt->i_physical_channels) == 0)
+            continue;
+
+        inlayout->mChannelDescriptions[chan_idx].mChannelLabel =
+            VlcChanToAudioChannelLabel(vlcchan, swap_rear_surround);
+        inlayout->mChannelDescriptions[chan_idx].mChannelFlags =
+            kAudioChannelFlags_AllOff;
+        chan_idx++;
+    }
+
+    msg_Dbg(p_aout, "VLC keeping the same input layout");
+
+    return VLC_SUCCESS;
+}


=====================================
modules/audio_output/apple/channel_layout.h
=====================================
@@ -0,0 +1,25 @@
+/*****************************************************************************
+ * channel_layout.h: Common Channel Layout code for iOS and macOS
+ *****************************************************************************
+ * Copyright (C) 2024 VLC authors and VideoLAN
+ *
+ * This program 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.
+ *
+ * This program 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 this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
+ *****************************************************************************/
+
+typedef struct AudioChannelLayout AudioChannelLayout;
+
+int
+channel_layout_MapFromVLC(audio_output_t *p_aout, const audio_sample_format_t *fmt,
+                          AudioChannelLayout **inlayoutp, size_t *inlayout_size);


=====================================
modules/audio_output/coreaudio_common.c → modules/audio_output/apple/coreaudio_common.c
=====================================
@@ -22,8 +22,8 @@
  * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
  *****************************************************************************/
 
-#include <stdckdint.h>
 #include "coreaudio_common.h"
+#include "channel_layout.h"
 #include <CoreAudio/CoreAudioTypes.h>
 
 #define TIMING_REPORT_DELAY_TICKS VLC_TICK_FROM_MS(1000)
@@ -554,39 +554,6 @@ AudioChannelLabelToVlcChan(AudioChannelLabel chan, bool swap_rear_surround)
     }
 }
 
-static AudioChannelLabel
-VlcChanToAudioChannelLabel(unsigned chan, bool swap_rear_surround)
-{
-    /* maps auhal channels to vlc ones */
-    switch (chan)
-    {
-        case AOUT_CHAN_LEFT:
-            return kAudioChannelLabel_Left;
-        case AOUT_CHAN_RIGHT:
-            return kAudioChannelLabel_Right;
-        case AOUT_CHAN_CENTER:
-            return kAudioChannelLabel_Center;
-        case AOUT_CHAN_LFE:
-            return kAudioChannelLabel_LFEScreen;
-        case AOUT_CHAN_REARLEFT:
-            return swap_rear_surround ? kAudioChannelLabel_RearSurroundLeft
-                                      : kAudioChannelLabel_LeftSurround;
-        case AOUT_CHAN_REARRIGHT:
-            return swap_rear_surround ? kAudioChannelLabel_RearSurroundRight
-                                      : kAudioChannelLabel_RightSurround;
-        case AOUT_CHAN_MIDDLELEFT:
-            return swap_rear_surround ? kAudioChannelLabel_LeftSurround
-                                      : kAudioChannelLabel_RearSurroundLeft;
-        case AOUT_CHAN_MIDDLERIGHT:
-            return swap_rear_surround ? kAudioChannelLabel_RightSurround
-                                      : kAudioChannelLabel_RearSurroundRight;
-        case AOUT_CHAN_REARCENTER:
-            return kAudioChannelLabel_CenterSurround;
-        default:
-            vlc_assert_unreachable();
-    }
-}
-
 static int
 MapOutputLayout(audio_output_t *p_aout, audio_sample_format_t *fmt,
                 const AudioChannelLayout *outlayout, bool *warn_configuration)
@@ -693,48 +660,6 @@ MapOutputLayout(audio_output_t *p_aout, audio_sample_format_t *fmt,
     return VLC_SUCCESS;
 }
 
-static int
-MapInputLayout(audio_output_t *p_aout, const audio_sample_format_t *fmt,
-               AudioChannelLayout **inlayoutp, size_t *inlayout_size)
-{
-    unsigned channels = aout_FormatNbChannels(fmt);
-
-    size_t size;
-    if (ckd_mul(&size, channels, sizeof(AudioChannelDescription)) ||
-        ckd_add(&size, size, sizeof(AudioChannelLayout)))
-        return VLC_ENOMEM;
-    AudioChannelLayout *inlayout = malloc(size);
-    if (inlayout == NULL)
-        return VLC_ENOMEM;
-
-    *inlayoutp = inlayout;
-    *inlayout_size = size;
-    inlayout->mChannelLayoutTag = kAudioChannelLayoutTag_UseChannelDescriptions;
-    inlayout->mNumberChannelDescriptions = aout_FormatNbChannels(fmt);
-
-    bool swap_rear_surround = (fmt->i_physical_channels & AOUT_CHANS_7_0) == AOUT_CHANS_7_0;
-    if (swap_rear_surround)
-        msg_Dbg(p_aout, "swapping Surround and RearSurround channels "
-                "for 7.1 Rear Surround");
-    unsigned chan_idx = 0;
-    for (unsigned i = 0; i < AOUT_CHAN_MAX; ++i)
-    {
-        unsigned vlcchan = pi_vlc_chan_order_wg4[i];
-        if ((vlcchan & fmt->i_physical_channels) == 0)
-            continue;
-
-        inlayout->mChannelDescriptions[chan_idx].mChannelLabel =
-            VlcChanToAudioChannelLabel(vlcchan, swap_rear_surround);
-        inlayout->mChannelDescriptions[chan_idx].mChannelFlags =
-            kAudioChannelFlags_AllOff;
-        chan_idx++;
-    }
-
-    msg_Dbg(p_aout, "VLC keeping the same input layout");
-
-    return VLC_SUCCESS;
-}
-
 int
 au_Initialize(audio_output_t *p_aout, AudioUnit au, audio_sample_format_t *fmt,
               const AudioChannelLayout *outlayout, vlc_tick_t i_dev_latency_ticks,
@@ -764,7 +689,8 @@ au_Initialize(audio_output_t *p_aout, AudioUnit au, audio_sample_format_t *fmt,
         else
         {
             aout_FormatPrepare(fmt);
-            ret = MapInputLayout(p_aout, fmt, &inlayout_buf, &inlayout_size);
+            ret = channel_layout_MapFromVLC(p_aout, fmt, &inlayout_buf,
+                                            &inlayout_size);
             if (ret != VLC_SUCCESS)
                 return ret;
             inlayout = inlayout_buf;


=====================================
modules/audio_output/coreaudio_common.h → modules/audio_output/apple/coreaudio_common.h
=====================================


=====================================
modules/audio_output/meson.build
=====================================
@@ -50,7 +50,8 @@ endif
 if have_osx
     vlc_modules += {
         'name' : 'auhal',
-        'sources' : files('auhal.c', 'coreaudio_common.c'),
+        'sources' : files('apple/auhal.c', 'apple/coreaudio_common.c',
+                          'apple/channel_layout.c'),
         'dependencies' : [
             frameworks['CoreFoundation'],
             frameworks['AudioUnit'],
@@ -64,7 +65,8 @@ endif
 if have_ios or have_tvos
     vlc_modules += {
         'name' : 'audiounit_ios',
-        'sources' : files('audiounit_ios.m', 'coreaudio_common.c'),
+        'sources' : files('apple/audiounit_ios.m', 'apple/coreaudio_common.c',
+                          'apple/channel_layout.c', 'apple/avaudiosession_common.m'),
         'dependencies' : [
             frameworks['Foundation'],
             frameworks['AVFoundation'],
@@ -73,3 +75,26 @@ if have_ios or have_tvos
         ],
     }
 endif
+
+if have_osx or have_ios or have_tvos
+    avsamplebuffer_sources = files(
+        'apple/avsamplebuffer.m',
+        'apple/channel_layout.c',
+    )
+    if have_ios or have_tvos
+        avsamplebuffer_sources += files(
+            'apple/avaudiosession_common.m',
+        )
+    endif
+
+    vlc_modules += {
+        'name' : 'avsamplebuffer',
+        'sources' : avsamplebuffer_sources,
+        'dependencies' : [
+            frameworks['CoreMedia'],
+            frameworks['Foundation'],
+            frameworks['AVFoundation'],
+        ]
+        'objc_args' : ['-fobjc-arc']
+    }
+endif


=====================================
po/POTFILES.in
=====================================
@@ -250,9 +250,10 @@ modules/audio_output/alsa.c
 modules/audio_output/amem.c
 modules/audio_output/android/device.c
 modules/audio_output/android/opensles.c
-modules/audio_output/audiounit_ios.m
-modules/audio_output/auhal.c
-modules/audio_output/coreaudio_common.c
+modules/audio_output/apple/audiounit_ios.m
+modules/audio_output/apple/avsamplebuffer.m
+modules/audio_output/apple/auhal.c
+modules/audio_output/apple/coreaudio_common.c
 modules/audio_output/file.c
 modules/audio_output/jack.c
 modules/audio_output/kai.c



View it on GitLab: https://code.videolan.org/videolan/vlc/-/compare/c7b294d94cf84ca615e3660aeec0ba99dfcfafa9...63f3e39652a7179b5d07c968471b5601810fc3a8

-- 
View it on GitLab: https://code.videolan.org/videolan/vlc/-/compare/c7b294d94cf84ca615e3660aeec0ba99dfcfafa9...63f3e39652a7179b5d07c968471b5601810fc3a8
You're receiving this email because of your account on code.videolan.org.


VideoLAN code repository instance


More information about the vlc-commits mailing list