mirror of https://code.videolan.org/videolan/vlc
Merge branch 'avsamplebuffer' into 'master'
aout: apple: add avsamplebuffer module See merge request videolan/vlc!5307
This commit is contained in:
commit
f7ed61de75
|
@ -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
|
||||
|
|
|
@ -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)
|
|
@ -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);
|
|
@ -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;
|
||||
}
|
|
@ -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 ()
|
|
@ -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;
|
||||
}
|
|
@ -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);
|
|
@ -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;
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue