ao_avfoundation: initial avfoundation ao support

This commit is contained in:
Misaki Kasumi 2024-03-17 04:14:47 +08:00 committed by der richter
parent 7ab1080749
commit 1ed8607292
10 changed files with 597 additions and 26 deletions

View File

@ -138,11 +138,21 @@ Available audio output drivers are:
passthrough (even if the device reports it as supported). Use with passthrough (even if the device reports it as supported). Use with
extreme care. extreme care.
``coreaudio_exclusive`` (macOS only) ``coreaudio_exclusive`` (macOS only)
Native macOS audio output driver using direct device access and Native macOS audio output driver using direct device access and
exclusive mode (bypasses the sound server). exclusive mode (bypasses the sound server).
``avfoundation`` (macOS only)
Native macOS audio output driver using ``AVSampleBufferAudioRenderer``
in AVFoundation, which supports `spatial audio
<https://support.apple.com/en-us/HT211775>`_.
.. warning::
Turning on spatial audio may hang the playback
if mpv is not started out of the bundle,
though playback with spatial audio off always works.
``openal`` ``openal``
OpenAL audio output driver. OpenAL audio output driver.

View File

@ -40,6 +40,7 @@ extern const struct ao_driver audio_out_audiotrack;
extern const struct ao_driver audio_out_audiounit; extern const struct ao_driver audio_out_audiounit;
extern const struct ao_driver audio_out_coreaudio; extern const struct ao_driver audio_out_coreaudio;
extern const struct ao_driver audio_out_coreaudio_exclusive; extern const struct ao_driver audio_out_coreaudio_exclusive;
extern const struct ao_driver audio_out_avfoundation;
extern const struct ao_driver audio_out_rsound; extern const struct ao_driver audio_out_rsound;
extern const struct ao_driver audio_out_pipewire; extern const struct ao_driver audio_out_pipewire;
extern const struct ao_driver audio_out_sndio; extern const struct ao_driver audio_out_sndio;
@ -65,6 +66,9 @@ static const struct ao_driver * const audio_out_drivers[] = {
#if HAVE_COREAUDIO #if HAVE_COREAUDIO
&audio_out_coreaudio, &audio_out_coreaudio,
#endif #endif
#if HAVE_AVFOUNDATION
&audio_out_avfoundation,
#endif
#if HAVE_PIPEWIRE #if HAVE_PIPEWIRE
&audio_out_pipewire, &audio_out_pipewire,
#endif #endif

358
audio/out/ao_avfoundation.m Normal file
View File

@ -0,0 +1,358 @@
/*
* This file is part of mpv.
*
* mpv is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* mpv is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with mpv. If not, see <http://www.gnu.org/licenses/>.
*/
#include "ao.h"
#include "audio/format.h"
#include "audio/out/ao_coreaudio_chmap.h"
#include "audio/out/ao_coreaudio_utils.h"
#include "common/common.h"
#include "common/msg.h"
#include "internal.h"
#include "osdep/timer.h"
#include "ta/ta_talloc.h"
#import <AVFoundation/AVFoundation.h>
#import <Foundation/Foundation.h>
#import <CoreAudioTypes/CoreAudioTypes.h>
#import <CoreFoundation/CoreFoundation.h>
#import <CoreMedia/CoreMedia.h>
@interface AVObserver : NSObject {
struct ao *ao;
}
- (void)handleRestartNotification:(NSNotification*)notification;
@end
struct priv {
AVSampleBufferAudioRenderer *renderer;
AVSampleBufferRenderSynchronizer *synchronizer;
dispatch_queue_t queue;
CMAudioFormatDescriptionRef format_description;
AVObserver *observer;
int64_t end_time_av;
};
static int64_t CMTimeGetNanoseconds(CMTime time)
{
time = CMTimeConvertScale(time, 1000000000, kCMTimeRoundingMethod_Default);
return time.value;
}
static CMTime CMTimeFromNanoseconds(int64_t time)
{
return CMTimeMake(time, 1000000000);
}
static void feed(struct ao *ao)
{
struct priv *p = ao->priv;
int samplerate = ao->samplerate;
int sstride = ao->sstride;
CMBlockBufferRef block_buffer = NULL;
CMSampleBufferRef sample_buffer = NULL;
OSStatus err;
int request_sample_count = samplerate / 10;
int buffer_size = request_sample_count * sstride;
void *data[] = {CFAllocatorAllocate(NULL, buffer_size, 0)};
int64_t cur_time_av = CMTimeGetNanoseconds([p->synchronizer currentTime]);
int64_t cur_time_mp = mp_time_ns();
int64_t end_time_av = MPMAX(p->end_time_av, cur_time_av);
int64_t time_delta = CMTimeGetNanoseconds(CMTimeMake(request_sample_count, samplerate));
int real_sample_count = ao_read_data_nonblocking(ao, data, request_sample_count, end_time_av - cur_time_av + cur_time_mp + time_delta);
if (real_sample_count == 0) {
// avoid spinning by blocking the thread
mp_sleep_ns(10000000);
goto finish;
}
if ((err = CMBlockBufferCreateWithMemoryBlock(
NULL,
data[0],
buffer_size,
NULL,
NULL,
0,
real_sample_count * sstride,
0,
&block_buffer
)) != noErr) {
MP_FATAL(ao, "failed to create block buffer\n");
MP_VERBOSE(ao, "CMBlockBufferCreateWithMemoryBlock returned %d\n", err);
goto error;
}
data[0] = NULL;
CMSampleTimingInfo sample_timing_into[] = {(CMSampleTimingInfo) {
.duration = CMTimeMake(1, samplerate),
.presentationTimeStamp = CMTimeFromNanoseconds(end_time_av),
.decodeTimeStamp = kCMTimeInvalid
}};
size_t sample_size_array[] = {sstride};
if ((err = CMSampleBufferCreateReady(
NULL,
block_buffer,
p->format_description,
real_sample_count,
1,
sample_timing_into,
1,
sample_size_array,
&sample_buffer
)) != noErr) {
MP_FATAL(ao, "failed to create sample buffer\n");
MP_VERBOSE(ao, "CMSampleBufferCreateReady returned %d\n", err);
goto error;
}
[p->renderer enqueueSampleBuffer:sample_buffer];
time_delta = CMTimeGetNanoseconds(CMTimeMake(real_sample_count, samplerate));
p->end_time_av = end_time_av + time_delta;
goto finish;
error:
ao_request_reload(ao);
finish:
if (data[0]) CFAllocatorDeallocate(NULL, data[0]);
if (block_buffer) CFRelease(block_buffer);
if (sample_buffer) CFRelease(sample_buffer);
}
static void start(struct ao *ao)
{
struct priv *p = ao->priv;
p->end_time_av = -1;
[p->synchronizer setRate:1];
[p->renderer requestMediaDataWhenReadyOnQueue:p->queue usingBlock:^{
feed(ao);
}];
}
static void stop(struct ao *ao)
{
struct priv *p = ao->priv;
dispatch_sync(p->queue, ^{
[p->renderer stopRequestingMediaData];
[p->renderer flush];
[p->synchronizer setRate:0];
});
}
static int control(struct ao *ao, enum aocontrol cmd, void *arg)
{
struct priv *p = ao->priv;
switch (cmd) {
case AOCONTROL_GET_MUTE:
*(bool*)arg = [p->renderer isMuted];
return CONTROL_OK;
case AOCONTROL_GET_VOLUME:
*(float*)arg = [p->renderer volume] * 100;
return CONTROL_OK;
case AOCONTROL_SET_MUTE:
[p->renderer setMuted:*(bool*)arg];
return CONTROL_OK;
case AOCONTROL_SET_VOLUME:
[p->renderer setVolume:*(float*)arg / 100];
return CONTROL_OK;
default:
return CONTROL_UNKNOWN;
}
}
@implementation AVObserver
- (instancetype)initWithAO:(struct ao*)_ao {
self = [super init];
if (self) {
ao = _ao;
}
return self;
}
- (void)handleRestartNotification:(NSNotification*)notification {
char *name = cfstr_get_cstr((CFStringRef)notification.name);
MP_WARN(ao, "restarting due to system notification; this will cause desync\n");
MP_VERBOSE(ao, "notification name: %s\n", name);
talloc_free(name);
stop(ao);
start(ao);
}
@end
static int init(struct ao *ao)
{
struct priv *p = ao->priv;
AudioChannelLayout *layout = NULL;
#if TARGET_OS_IPHONE
AVAudioSession *instance = AVAudioSession.sharedInstance;
NSInteger maxChannels = instance.maximumOutputNumberOfChannels;
NSInteger prefChannels = MIN(maxChannels, ao->channels.num);
[instance setCategory:AVAudioSessionCategoryPlayback error:nil];
[instance setMode:AVAudioSessionModeMoviePlayback error:nil];
[instance setActive:YES error:nil];
[instance setPreferredOutputNumberOfChannels:prefChannels error:nil];
#endif
if ((p->renderer = [[AVSampleBufferAudioRenderer alloc] init]) == nil) {
MP_FATAL(ao, "failed to create audio renderer\n");
MP_VERBOSE(ao, "AVSampleBufferAudioRenderer failed to initialize\n");
goto error;
}
if ((p->synchronizer = [[AVSampleBufferRenderSynchronizer alloc] init]) == nil) {
MP_FATAL(ao, "failed to create rendering synchronizer\n");
MP_VERBOSE(ao, "AVSampleBufferRenderSynchronizer failed to initialize\n");
goto error;
}
if ((p->queue = dispatch_queue_create(
"avfoundation event",
dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_USER_INTERACTIVE, 0)
)) == NULL) {
MP_FATAL(ao, "failed to create dispatch queue\n");
MP_VERBOSE(ao, "dispatch_queue_create failed\n");
goto error;
}
if (ao->device && ao->device[0]) {
[p->renderer setAudioOutputDeviceUniqueID:(NSString*)cfstr_from_cstr(ao->device)];
}
[p->synchronizer addRenderer:p->renderer];
if (@available(tvOS 14.5, iOS 14.5, macOS 11.3, *)) {
[p->synchronizer setDelaysRateChangeUntilHasSufficientMediaData:NO];
}
if (af_fmt_is_spdif(ao->format)) {
MP_FATAL(ao, "avfoundation does not support SPDIF\n");
#if HAVE_COREAUDIO
MP_FATAL(ao, "please use coreaudio_exclusive instead\n");
#endif
goto error;
}
// AVSampleBufferAudioRenderer only supports interleaved formats
ao->format = af_fmt_from_planar(ao->format);
if (af_fmt_is_planar(ao->format)) {
MP_FATAL(ao, "planar audio formats are unsupported\n");
goto error;
}
AudioStreamBasicDescription asbd;
ca_fill_asbd(ao, &asbd);
size_t layout_size = sizeof(AudioChannelLayout)
+ (ao->channels.num - 1) * sizeof(AudioChannelDescription);
layout = talloc_size(ao, layout_size);
layout->mChannelLayoutTag = kAudioChannelLayoutTag_UseChannelDescriptions;
layout->mNumberChannelDescriptions = ao->channels.num;
for (int i = 0; i < ao->channels.num; ++i) {
AudioChannelDescription *desc = layout->mChannelDescriptions + i;
desc->mChannelFlags = kAudioChannelFlags_AllOff;
desc->mChannelLabel = mp_speaker_id_to_ca_label(ao->channels.speaker[i]);
}
void *talloc_ctx = talloc_new(NULL);
AudioChannelLayout *std_layout = ca_find_standard_layout(talloc_ctx, layout);
memmove(layout, std_layout, sizeof(AudioChannelLayout));
talloc_free(talloc_ctx);
ca_log_layout(ao, MSGL_V, layout);
OSStatus err;
if ((err = CMAudioFormatDescriptionCreate(
NULL,
&asbd,
layout_size,
layout,
0,
NULL,
NULL,
&p->format_description
)) != noErr) {
MP_FATAL(ao, "failed to create audio format description\n");
MP_VERBOSE(ao, "CMAudioFormatDescriptionCreate returned %d\n", err);
goto error;
}
talloc_free(layout);
layout = NULL;
// AVSampleBufferAudioRenderer read ahead aggressively
ao->device_buffer = ao->samplerate * 2;
p->observer = [[AVObserver alloc] initWithAO:ao];
NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
[center addObserver:p->observer selector:@selector(handleRestartNotification:) name:AVSampleBufferAudioRendererOutputConfigurationDidChangeNotification object:p->renderer];
[center addObserver:p->observer selector:@selector(handleRestartNotification:) name:AVSampleBufferAudioRendererWasFlushedAutomaticallyNotification object:p->renderer];
return CONTROL_OK;
error:
talloc_free(layout);
if (p->renderer) [p->renderer release];
if (p->synchronizer) [p->synchronizer release];
if (p->queue) dispatch_release(p->queue);
if (p->format_description) CFRelease(p->format_description);
#if TARGET_OS_IPHONE
[AVAudioSession.sharedInstance setActive:NO
withOptions:AVAudioSessionSetActiveOptionNotifyOthersOnDeactivation
error:nil
];
#endif
return CONTROL_ERROR;
}
static void uninit(struct ao *ao)
{
struct priv *p = ao->priv;
stop(ao);
[p->renderer release];
[p->synchronizer release];
dispatch_release(p->queue);
CFRelease(p->format_description);
[[NSNotificationCenter defaultCenter] removeObserver:p->observer];
[p->observer release];
#if TARGET_OS_IPHONE
[AVAudioSession.sharedInstance setActive:NO
withOptions:AVAudioSessionSetActiveOptionNotifyOthersOnDeactivation
error:nil
];
#endif
}
#define OPT_BASE_STRUCT struct priv
const struct ao_driver audio_out_avfoundation = {
.description = "AVFoundation AVSampleBufferAudioRenderer",
.name = "avfoundation",
.uninit = uninit,
.init = init,
.control = control,
.reset = stop,
.start = start,
.list_devs = ca_get_device_list,
.priv_size = sizeof(struct priv),
};

View File

@ -22,6 +22,7 @@
#include "ao_coreaudio_utils.h" #include "ao_coreaudio_utils.h"
#include "ao_coreaudio_chmap.h" #include "ao_coreaudio_chmap.h"
#include <CoreAudioTypes/CoreAudioTypes.h>
static const int speaker_map[][2] = { static const int speaker_map[][2] = {
{ kAudioChannelLabel_Left, MP_SPEAKER_ID_FL }, { kAudioChannelLabel_Left, MP_SPEAKER_ID_FL },
@ -65,6 +66,119 @@ static const int speaker_map[][2] = {
{ 0, -1 }, { 0, -1 },
}; };
static const AudioChannelLayoutTag std_layouts[] = {
(100U<<16) | 1, // kAudioChannelLayoutTag_Mono
(101U<<16) | 2, // kAudioChannelLayoutTag_Stereo
(102U<<16) | 2, // kAudioChannelLayoutTag_StereoHeadphones
(103U<<16) | 2, // kAudioChannelLayoutTag_MatrixStereo
(104U<<16) | 2, // kAudioChannelLayoutTag_MidSide
(105U<<16) | 2, // kAudioChannelLayoutTag_XY
(106U<<16) | 2, // kAudioChannelLayoutTag_Binaural
(107U<<16) | 4, // kAudioChannelLayoutTag_Ambisonic_B_Format
(108U<<16) | 4, // kAudioChannelLayoutTag_Quadraphonic
(109U<<16) | 5, // kAudioChannelLayoutTag_Pentagonal
(110U<<16) | 6, // kAudioChannelLayoutTag_Hexagonal
(111U<<16) | 8, // kAudioChannelLayoutTag_Octagonal
(112U<<16) | 8, // kAudioChannelLayoutTag_Cube
(113U<<16) | 3, // kAudioChannelLayoutTag_MPEG_3_0_A
(114U<<16) | 3, // kAudioChannelLayoutTag_MPEG_3_0_B
(115U<<16) | 4, // kAudioChannelLayoutTag_MPEG_4_0_A
(116U<<16) | 4, // kAudioChannelLayoutTag_MPEG_4_0_B
(117U<<16) | 5, // kAudioChannelLayoutTag_MPEG_5_0_A
(118U<<16) | 5, // kAudioChannelLayoutTag_MPEG_5_0_B
(119U<<16) | 5, // kAudioChannelLayoutTag_MPEG_5_0_C
(120U<<16) | 5, // kAudioChannelLayoutTag_MPEG_5_0_D
(121U<<16) | 6, // kAudioChannelLayoutTag_MPEG_5_1_A
(122U<<16) | 6, // kAudioChannelLayoutTag_MPEG_5_1_B
(123U<<16) | 6, // kAudioChannelLayoutTag_MPEG_5_1_C
(124U<<16) | 6, // kAudioChannelLayoutTag_MPEG_5_1_D
(125U<<16) | 7, // kAudioChannelLayoutTag_MPEG_6_1_A
(126U<<16) | 8, // kAudioChannelLayoutTag_MPEG_7_1_A
(127U<<16) | 8, // kAudioChannelLayoutTag_MPEG_7_1_B
(128U<<16) | 8, // kAudioChannelLayoutTag_MPEG_7_1_C
(129U<<16) | 8, // kAudioChannelLayoutTag_Emagic_Default_7_1
(130U<<16) | 8, // kAudioChannelLayoutTag_SMPTE_DTV
(131U<<16) | 3, // kAudioChannelLayoutTag_ITU_2_1
(132U<<16) | 4, // kAudioChannelLayoutTag_ITU_2_2
(133U<<16) | 3, // kAudioChannelLayoutTag_DVD_4
(134U<<16) | 4, // kAudioChannelLayoutTag_DVD_5
(135U<<16) | 5, // kAudioChannelLayoutTag_DVD_6
(136U<<16) | 4, // kAudioChannelLayoutTag_DVD_10
(137U<<16) | 5, // kAudioChannelLayoutTag_DVD_11
(138U<<16) | 5, // kAudioChannelLayoutTag_DVD_18
(139U<<16) | 6, // kAudioChannelLayoutTag_AudioUnit_6_0
(140U<<16) | 7, // kAudioChannelLayoutTag_AudioUnit_7_0
(148U<<16) | 7, // kAudioChannelLayoutTag_AudioUnit_7_0_Front
(141U<<16) | 6, // kAudioChannelLayoutTag_AAC_6_0
(142U<<16) | 7, // kAudioChannelLayoutTag_AAC_6_1
(143U<<16) | 7, // kAudioChannelLayoutTag_AAC_7_0
(183U<<16) | 8, // kAudioChannelLayoutTag_AAC_7_1_B
(184U<<16) | 8, // kAudioChannelLayoutTag_AAC_7_1_C
(144U<<16) | 8, // kAudioChannelLayoutTag_AAC_Octagonal
(145U<<16) | 16, // kAudioChannelLayoutTag_TMH_10_2_std
(146U<<16) | 21, // kAudioChannelLayoutTag_TMH_10_2_full
(149U<<16) | 2, // kAudioChannelLayoutTag_AC3_1_0_1
(150U<<16) | 3, // kAudioChannelLayoutTag_AC3_3_0
(151U<<16) | 4, // kAudioChannelLayoutTag_AC3_3_1
(152U<<16) | 4, // kAudioChannelLayoutTag_AC3_3_0_1
(153U<<16) | 4, // kAudioChannelLayoutTag_AC3_2_1_1
(154U<<16) | 5, // kAudioChannelLayoutTag_AC3_3_1_1
(155U<<16) | 6, // kAudioChannelLayoutTag_EAC_6_0_A
(156U<<16) | 7, // kAudioChannelLayoutTag_EAC_7_0_A
(157U<<16) | 7, // kAudioChannelLayoutTag_EAC3_6_1_A
(158U<<16) | 7, // kAudioChannelLayoutTag_EAC3_6_1_B
(159U<<16) | 7, // kAudioChannelLayoutTag_EAC3_6_1_C
(160U<<16) | 8, // kAudioChannelLayoutTag_EAC3_7_1_A
(161U<<16) | 8, // kAudioChannelLayoutTag_EAC3_7_1_B
(162U<<16) | 8, // kAudioChannelLayoutTag_EAC3_7_1_C
(163U<<16) | 8, // kAudioChannelLayoutTag_EAC3_7_1_D
(164U<<16) | 8, // kAudioChannelLayoutTag_EAC3_7_1_E
(165U<<16) | 8, // kAudioChannelLayoutTag_EAC3_7_1_F
(166U<<16) | 8, // kAudioChannelLayoutTag_EAC3_7_1_G
(167U<<16) | 8, // kAudioChannelLayoutTag_EAC3_7_1_H
(168U<<16) | 4, // kAudioChannelLayoutTag_DTS_3_1
(169U<<16) | 5, // kAudioChannelLayoutTag_DTS_4_1
(170U<<16) | 6, // kAudioChannelLayoutTag_DTS_6_0_A
(171U<<16) | 6, // kAudioChannelLayoutTag_DTS_6_0_B
(172U<<16) | 6, // kAudioChannelLayoutTag_DTS_6_0_C
(173U<<16) | 7, // kAudioChannelLayoutTag_DTS_6_1_A
(174U<<16) | 7, // kAudioChannelLayoutTag_DTS_6_1_B
(175U<<16) | 7, // kAudioChannelLayoutTag_DTS_6_1_C
(176U<<16) | 7, // kAudioChannelLayoutTag_DTS_7_0
(177U<<16) | 8, // kAudioChannelLayoutTag_DTS_7_1
(178U<<16) | 8, // kAudioChannelLayoutTag_DTS_8_0_A
(179U<<16) | 8, // kAudioChannelLayoutTag_DTS_8_0_B
(180U<<16) | 9, // kAudioChannelLayoutTag_DTS_8_1_A
(181U<<16) | 9, // kAudioChannelLayoutTag_DTS_8_1_B
(182U<<16) | 7, // kAudioChannelLayoutTag_DTS_6_1_D
(185U<<16) | 4, // kAudioChannelLayoutTag_WAVE_4_0_B
(186U<<16) | 5, // kAudioChannelLayoutTag_WAVE_5_0_B
(187U<<16) | 6, // kAudioChannelLayoutTag_WAVE_5_1_B
(188U<<16) | 7, // kAudioChannelLayoutTag_WAVE_6_1
(189U<<16) | 8, // kAudioChannelLayoutTag_WAVE_7_1
(194U<<16) | 8, // kAudioChannelLayoutTag_Atmos_5_1_2
(195U<<16) | 10, // kAudioChannelLayoutTag_Atmos_5_1_4
(196U<<16) | 10, // kAudioChannelLayoutTag_Atmos_7_1_2
(192U<<16) | 12, // kAudioChannelLayoutTag_Atmos_7_1_4
(193U<<16) | 16, // kAudioChannelLayoutTag_Atmos_9_1_6
(197U<<16) | 4, // kAudioChannelLayoutTag_Logic_4_0_C
(198U<<16) | 6, // kAudioChannelLayoutTag_Logic_6_0_B
(199U<<16) | 7, // kAudioChannelLayoutTag_Logic_6_1_B
(200U<<16) | 7, // kAudioChannelLayoutTag_Logic_6_1_D
(201U<<16) | 8, // kAudioChannelLayoutTag_Logic_7_1_B
(202U<<16) | 12, // kAudioChannelLayoutTag_Logic_Atmos_7_1_4_B
(203U<<16) | 14, // kAudioChannelLayoutTag_Logic_Atmos_7_1_6
(204U<<16) | 24, // kAudioChannelLayoutTag_CICP_13
(205U<<16) | 8, // kAudioChannelLayoutTag_CICP_14
(206U<<16) | 12, // kAudioChannelLayoutTag_CICP_15
(207U<<16) | 10, // kAudioChannelLayoutTag_CICP_16
(208U<<16) | 12, // kAudioChannelLayoutTag_CICP_17
(209U<<16) | 14, // kAudioChannelLayoutTag_CICP_18
(210U<<16) | 12, // kAudioChannelLayoutTag_CICP_19
(211U<<16) | 14, // kAudioChannelLayoutTag_CICP_20
kAudioChannelLayoutTag_Unknown
};
int ca_label_to_mp_speaker_id(AudioChannelLabel label) int ca_label_to_mp_speaker_id(AudioChannelLabel label)
{ {
for (int i = 0; speaker_map[i][1] >= 0; i++) for (int i = 0; speaker_map[i][1] >= 0; i++)
@ -73,30 +187,48 @@ int ca_label_to_mp_speaker_id(AudioChannelLabel label)
return -1; return -1;
} }
AudioChannelLabel mp_speaker_id_to_ca_label(int speaker_id)
{
for (int i = 0; speaker_map[i][1] >= 0; i++)
if (speaker_map[i][1] == speaker_id)
return speaker_map[i][0];
return -1; // kAudioChannelLabel_Unknown
}
#if HAVE_COREAUDIO #if HAVE_COREAUDIO
static void ca_log_layout(struct ao *ao, int l, AudioChannelLayout *layout) void ca_log_layout(struct ao *ao, int l, AudioChannelLayout *layout)
{ {
if (!mp_msg_test(ao->log, l)) if (!mp_msg_test(ao->log, l))
return; return;
AudioChannelDescription *descs = layout->mChannelDescriptions; AudioChannelLayoutTag tag = layout->mChannelLayoutTag;
mp_msg(ao->log, l, "audio channel layout: tag: <%u>", tag);
mp_msg(ao->log, l, "layout: tag: <%u>, bitmap: <%u>, " if (tag == kAudioChannelLayoutTag_UseChannelDescriptions) {
"descriptions <%u>\n", AudioChannelDescription *descs = layout->mChannelDescriptions;
(unsigned) layout->mChannelLayoutTag, mp_msg(ao->log, l, ", descriptions <%u>\n",
(unsigned) layout->mChannelBitmap, (unsigned) layout->mNumberChannelDescriptions);
(unsigned) layout->mNumberChannelDescriptions);
for (int i = 0; i < layout->mNumberChannelDescriptions; i++) { for (int i = 0; i < layout->mNumberChannelDescriptions; i++) {
AudioChannelDescription d = descs[i]; AudioChannelDescription d = descs[i];
mp_msg(ao->log, l, " - description %d: label <%u, %u>, " mp_msg(ao->log, l, " - description %d: label <%u, %u>, flags: <%u>",
" flags: <%u>, coords: <%f, %f, %f>\n", i, i,
(unsigned) d.mChannelLabel, (unsigned) d.mChannelLabel,
(unsigned) ca_label_to_mp_speaker_id(d.mChannelLabel), (unsigned) ca_label_to_mp_speaker_id(d.mChannelLabel),
(unsigned) d.mChannelFlags, (unsigned) d.mChannelFlags);
d.mCoordinates[0], if (d.mChannelFlags != kAudioChannelFlags_AllOff) {
d.mCoordinates[1], mp_msg(ao->log, l, ", coords: <%f, %f, %f>\n",
d.mCoordinates[2]); d.mCoordinates[0],
d.mCoordinates[1],
d.mCoordinates[2]);
} else {
mp_msg(ao->log, l, "\n");
}
}
} else if (tag == kAudioChannelLayoutTag_UseChannelBitmap) {
mp_msg(ao->log, l, ", bitmap <%u>\n", layout->mChannelBitmap);
} else {
mp_msg(ao->log, l, "\n");
} }
} }
@ -117,7 +249,7 @@ static AudioChannelLayout *ca_layout_to_custom_layout(struct ao *ao,
kAudioFormatProperty_ChannelLayoutForBitmap, kAudioFormatProperty_ChannelLayoutForBitmap,
sizeof(uint32_t), &l->mChannelBitmap, &psize); sizeof(uint32_t), &l->mChannelBitmap, &psize);
CHECK_CA_ERROR("failed to convert channel bitmap to descriptions (info)"); CHECK_CA_ERROR("failed to convert channel bitmap to descriptions (info)");
r = talloc_size(NULL, psize); r = talloc_size(talloc_ctx, psize);
err = AudioFormatGetProperty( err = AudioFormatGetProperty(
kAudioFormatProperty_ChannelLayoutForBitmap, kAudioFormatProperty_ChannelLayoutForBitmap,
sizeof(uint32_t), &l->mChannelBitmap, &psize, r); sizeof(uint32_t), &l->mChannelBitmap, &psize, r);
@ -127,7 +259,7 @@ static AudioChannelLayout *ca_layout_to_custom_layout(struct ao *ao,
err = AudioFormatGetPropertyInfo( err = AudioFormatGetPropertyInfo(
kAudioFormatProperty_ChannelLayoutForTag, kAudioFormatProperty_ChannelLayoutForTag,
sizeof(AudioChannelLayoutTag), &l->mChannelLayoutTag, &psize); sizeof(AudioChannelLayoutTag), &l->mChannelLayoutTag, &psize);
r = talloc_size(NULL, psize); r = talloc_size(talloc_ctx, psize);
CHECK_CA_ERROR("failed to convert channel tag to descriptions (info)"); CHECK_CA_ERROR("failed to convert channel tag to descriptions (info)");
err = AudioFormatGetProperty( err = AudioFormatGetProperty(
kAudioFormatProperty_ChannelLayoutForTag, kAudioFormatProperty_ChannelLayoutForTag,
@ -135,14 +267,53 @@ static AudioChannelLayout *ca_layout_to_custom_layout(struct ao *ao,
CHECK_CA_ERROR("failed to convert channel tag to descriptions (get)"); CHECK_CA_ERROR("failed to convert channel tag to descriptions (get)");
} }
MP_VERBOSE(ao, "converted input channel layout:\n"); if (ao) {
ca_log_layout(ao, MSGL_V, l); MP_VERBOSE(ao, "converted input channel layout:\n");
ca_log_layout(ao, MSGL_V, l);
}
return r; return r;
coreaudio_error: coreaudio_error:
return NULL; return NULL;
} }
AudioChannelLayout *ca_find_standard_layout(void *talloc_ctx, AudioChannelLayout *l)
{
if (l->mChannelLayoutTag != kAudioChannelLayoutTag_UseChannelDescriptions)
return l;
AudioChannelLayout *s = talloc_size(talloc_ctx, sizeof(AudioChannelLayout));
for (int i = 0; ; ++i) {
if ((s->mChannelLayoutTag = std_layouts[i]) == kAudioChannelLayoutTag_Unknown) {
s = NULL;
break;
}
AudioChannelLayout *r = ca_layout_to_custom_layout(NULL, talloc_ctx, s);
if (!r)
goto mismatch;
if (l->mNumberChannelDescriptions != r->mNumberChannelDescriptions)
goto mismatch;
for (int i = 0; i < l->mNumberChannelDescriptions; ++i) {
AudioChannelDescription *ld = l->mChannelDescriptions + i;
AudioChannelDescription *rd = r->mChannelDescriptions + i;
if (ld->mChannelLabel == rd->mChannelLabel)
continue;
// XXX: we cannot handle channels with coordinates
goto mismatch;
}
break;
mismatch:;
}
return s ? s : l;
}
#define CHMAP(n, ...) &(struct mp_chmap) MP_CONCAT(MP_CHMAP, n) (__VA_ARGS__) #define CHMAP(n, ...) &(struct mp_chmap) MP_CONCAT(MP_CHMAP, n) (__VA_ARGS__)

View File

@ -18,15 +18,22 @@
#ifndef MPV_COREAUDIO_CHMAP_H #ifndef MPV_COREAUDIO_CHMAP_H
#define MPV_COREAUDIO_CHMAP_H #define MPV_COREAUDIO_CHMAP_H
#include "config.h"
#include <AudioToolbox/AudioToolbox.h> #include <AudioToolbox/AudioToolbox.h>
#include "config.h" #if HAVE_AVFOUNDATION || HAVE_AUDIOUNIT
#undef HAVE_COREAUDIO
#define HAVE_COREAUDIO 1
#endif
struct mp_chmap; struct mp_chmap;
int ca_label_to_mp_speaker_id(AudioChannelLabel label); int ca_label_to_mp_speaker_id(AudioChannelLabel label);
AudioChannelLabel mp_speaker_id_to_ca_label(int speaker_id);
#if HAVE_COREAUDIO #if HAVE_COREAUDIO
AudioChannelLayout *ca_find_standard_layout(void *talloc_ctx, AudioChannelLayout *l);
void ca_log_layout(struct ao *ao, int l, AudioChannelLayout *layout);
bool ca_init_chmap(struct ao *ao, AudioDeviceID device); bool ca_init_chmap(struct ao *ao, AudioDeviceID device);
void ca_get_active_chmap(struct ao *ao, AudioDeviceID device, int channel_count, void ca_get_active_chmap(struct ao *ao, AudioDeviceID device, int channel_count,
struct mp_chmap *out_map); struct mp_chmap *out_map);

View File

@ -23,6 +23,11 @@
#include "internal.h" #include "internal.h"
#if HAVE_AVFOUNDATION || HAVE_AUDIOUNIT
#undef HAVE_COREAUDIO
#define HAVE_COREAUDIO 1
#endif
// CoreAudio names are way too verbose // CoreAudio names are way too verbose
#define ca_sel AudioObjectPropertySelector #define ca_sel AudioObjectPropertySelector
#define ca_scope AudioObjectPropertyScope #define ca_scope AudioObjectPropertyScope

View File

@ -138,7 +138,8 @@ bool check_ca_st(struct ao *ao, int level, OSStatus code, const char *message)
{ {
if (code == noErr) return true; if (code == noErr) return true;
mp_msg(ao->log, level, "%s (%s/%d)\n", message, mp_tag_str(code), (int)code); if (ao)
mp_msg(ao->log, level, "%s (%s/%d)\n", message, mp_tag_str(code), (int)code);
return false; return false;
} }

View File

@ -29,6 +29,11 @@
#include "internal.h" #include "internal.h"
#include "osdep/utils-mac.h" #include "osdep/utils-mac.h"
#if HAVE_AVFOUNDATION || HAVE_AUDIOUNIT
#undef HAVE_COREAUDIO
#define HAVE_COREAUDIO 1
#endif
bool check_ca_st(struct ao *ao, int level, OSStatus code, const char *message); bool check_ca_st(struct ao *ao, int level, OSStatus code, const char *message);
#define CHECK_CA_ERROR_L(label, message) \ #define CHECK_CA_ERROR_L(label, message) \

View File

@ -799,6 +799,14 @@ if features['audiounit']
sources += files('audio/out/ao_audiounit.m') sources += files('audio/out/ao_audiounit.m')
endif endif
avfoundation = dependency('appleframeworks', modules: ['CoreMedia', 'AVFoundation'],
required: get_option('avfoundation'))
features += {'avfoundation': avfoundation.found()}
if features['avfoundation']
dependencies += avfoundation
sources += files('audio/out/ao_avfoundation.m')
endif
coreaudio = dependency('appleframeworks', modules: ['CoreFoundation', 'CoreAudio', coreaudio = dependency('appleframeworks', modules: ['CoreFoundation', 'CoreAudio',
'AudioUnit', 'AudioToolbox'], required: get_option('coreaudio')) 'AudioUnit', 'AudioToolbox'], required: get_option('coreaudio'))
features += {'coreaudio': coreaudio.found()} features += {'coreaudio': coreaudio.found()}
@ -809,9 +817,10 @@ if features['coreaudio']
'audio/out/ao_coreaudio_properties.c') 'audio/out/ao_coreaudio_properties.c')
endif endif
if features['audiounit'] or features['coreaudio'] if features['audiounit'] or features['coreaudio'] or features['avfoundation']
sources += files('audio/out/ao_coreaudio_chmap.c', sources += files('audio/out/ao_coreaudio_chmap.c',
'audio/out/ao_coreaudio_utils.c') 'audio/out/ao_coreaudio_utils.c',
'audio/out/ao_coreaudio_properties.c')
endif endif
jack_opt = get_option('jack').require( jack_opt = get_option('jack').require(

View File

@ -43,6 +43,7 @@ option('zlib', type: 'feature', value: 'auto', description: 'zlib')
option('alsa', type: 'feature', value: 'auto', description: 'ALSA audio output') option('alsa', type: 'feature', value: 'auto', description: 'ALSA audio output')
option('audiounit', type: 'feature', value: 'auto', description: 'AudioUnit output (iOS)') option('audiounit', type: 'feature', value: 'auto', description: 'AudioUnit output (iOS)')
option('coreaudio', type: 'feature', value: 'auto', description: 'CoreAudio audio output') option('coreaudio', type: 'feature', value: 'auto', description: 'CoreAudio audio output')
option('avfoundation', type: 'feature', value: 'auto', description: 'AVFoundation audio output')
option('jack', type: 'feature', value: 'auto', description: 'JACK audio output') option('jack', type: 'feature', value: 'auto', description: 'JACK audio output')
option('openal', type: 'feature', value: 'disabled', description: 'OpenAL audio output') option('openal', type: 'feature', value: 'disabled', description: 'OpenAL audio output')
option('opensles', type: 'feature', value: 'auto', description: 'OpenSL ES audio output') option('opensles', type: 'feature', value: 'auto', description: 'OpenSL ES audio output')