Merge branch 'avsamplebuffer' into 'master'

aout: apple: add avsamplebuffer module

See merge request videolan/vlc!5307
This commit is contained in:
Thomas Guillem 2024-05-07 06:35:27 +00:00
commit f7ed61de75
12 changed files with 1024 additions and 408 deletions

View File

@ -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

View File

@ -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";
@interface AVAudioSession (iOS15RoutingConfiguration)
- (BOOL)setSupportsMultichannelContent:(BOOL)inValue error:(NSError **)outError;
@end
@interface AVAudioSessionPortDescription (iOS15RoutingConfiguration)
@property (readonly, getter=isSpatialAudioEnabled) BOOL spatialAudioEnabled;
@end
#endif
@interface SessionManager : NSObject
{
NSMutableSet *_registeredInstances;
}
+ (SessionManager *)sharedInstance;
- (void)addAoutInstance:(AoutWrapper *)wrapperInstance;
- (NSInteger)removeAoutInstance:(AoutWrapper *)wrapperInstance;
@end
@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;
}
}
@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)

View File

@ -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);

View File

@ -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;
}

View File

@ -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))
@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;
}
@end
@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;
}
@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 ()

View File

@ -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;
}

View File

@ -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);

View File

@ -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;

View File

@ -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

View File

@ -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